#!/usr/local/bin/perl
use strict; use warnings;
no if $] >= 5.018, warnings => "experimental::smartmatch";
use feature qw/switch say/;

use Encode qw/decode encode find_encoding/;
use utf8;
use Cwd qw/abs_path/;
use Fcntl;
binmode STDIN,":utf8";
binmode STDOUT,":utf8";
binmode STDERR,":utf8";

use Glib qw/TRUE FALSE/;
use Gtk2 '-init';
use Gtk2::Gdk::Keysyms;
use Gtk2::Helper;

#-------------------------------------------------------------------

my $VERSION = "0.16.0b";
my ($me,$mydir,$you);
my ($fck_com,$rmdir_com);
my $devm;
my $pixdir;
my $snddir;
my $scrdir;
my $hokdir;
my ($ipcpath,$sockname,$echo_tty);
my ($mystdout,$mystderr);
BEGIN{
    $ENV{"IMVPID"}=$$;
    my $bp;
    if(-l $0){
	$me=readlink($0);
	unless(substr($me,0,1) eq "/"){
	    ($bp=$0)=~s/[^\/]*$/$me/;
	}else{
	    $bp=$me;
	}
    }else{
	$me=$0;
	$bp=abs_path($me);
    }
    $me=~s/.*\///;
    ($mydir=$bp)=~s/\/[^\/]*$//; # ignore special case $0=/imv
    $mydir=$ENV{"PWD"}."/".$mydir unless $mydir=~/^\//;
    push @INC, $mydir;
}

# image files
my @files;
my @arxvs;
my %opath;
my @dirs; # will never be cleared
my @thums;
my @gids;
my $kf;
my $ngpix;

# window status
my $fit          = ""; # W[=], f, fw, fh
my $fullscreen   = 0;
my $maximized    = 0;
my $above        = 0;

# image status
# ($sel,$gl,$ow,$oh,$hv,$vv,$f,$m,$r,$ll,$uu,$rr,$dd,$s,$w,$h,$ud,$rl)
my @stats;
my $sflags   = 0;
my $flipped  = 0;
my $mirrored = 0;
my $rot      = 0;
my $scale    = 1;
my $ha_value = 0;
my $va_value = 0;
my $pwidth   = 0;
my $pheight  = 0;
my $updown   = 1;
my $leftright= 0;
my $nselect  = 0;
my @lurd     = (0,0,0,0);
my $page_scan=-2; # 1 = hori, 2 = vert ,

# backup stack for files and stats.
my @baks;

# image status control
my @gstats;
my ($theA, $theB);
my $keep_rot      = 0;
my $keep_crop     = 0;
my $keep_flips    = 0;
my $keep_scale    = 0;
my $keep_size     = 0;
my $keep_pos      = 0;
my $keep_orient   = 0;
my $init_pos      = 0;
my $animate       = 0;
my $set_ani       = 0; # temp
my $need_init_pos = 0; # temp
my $init_pos_u    = 0; # temp

# status control params
my $dscale       = 0.06199740374526; # ((1+sqrt(5))/2)**(1/8)
my $dpx          = 10;
my $dpx_boost    = 4;
my $psidx        = 1;
my $screps       = 8;   #<<<

# geometry
my ($maxw,$maxh);
my ($cvsw,$cvsh)=(0,0);
my ($tnvw,$tnvh);
my $tphs=6;  #<<<< dontknowhowtoget "handle-size"
my ($defw,$defh)=(512,316);
my $tnvmode=-1; # [0-4] sep,left,up,right,bottom
my $hpage_skip;
my $vpage_skip;
my ($darew,$dareh);

# widget
my ($pix,$pixbak,$img);
my ($ppix,$ppixbak,$dare); # for slide mode
my ($win,$vbox,$swin,$logsw);
my ($log,$logw);
my $sbar;
my ($findw,$findb,$findl);
my ($comb,$comw,$coml);
my $thump;
my $fwcbid; # cb id for changed sig of $findw
my @opened;
my $sbg="black";
my $gsbg;
my $ptnv;
my ($tnvbgc,$tnvsbg);
my $tnvf;
my @pops;
my $slw; # slider width
my $ebox;

#
## /-------$win-------------\    /-------$win-------------\
## |/------$vbox-----------\|	 |/------$vbox-----------\|
## ||/=====$swin==========\||	 ||/=====$logsw=========\||
## ||I/----$img----------\I||	 ||I/----$logw---------\I||
## ||I|                  |I||	 ||I|                  |I||
## ||I|    $pix          |I||	 ||I|    $log          |I||
## ||I|                  |I||	 ||I|                  |I||
## ||I\------------------/I||	 ||I\------------------/I||
## ||\====================/||	 ||\====================/||
## ||                      ||	 ||                      ||
## ||/-----$sbar----------\||	 ||/-----$sbar----------\||
## |||                    |||	 |||                    |||
## ||\--------------------/||	 ||\--------------------/||
## |||                    |||	 |||                    |||
## ||/-----$findb---------\||	 ||/-----$findb---------\||
## |||/-$findw-\/-$findl-\|||	 |||/-$findw-\/-$findl-\|||
## ||||        ||        ||||	 ||||        ||        ||||
## |||\--------/\--------/|||	 |||\--------/\--------/|||
## ||\--------------------/||	 ||\--------------------/||
## |\----------------------/|	 |\----------------------/|
## \------------------------/	 \------------------------/
##
## /-------$win---------------------------\
## |/------$thump------------------------\|
## ||/--$$ptnv--\/------$vbox-----------\||
## |||          ||/=====$swin==========\|||
## |||          ||I/----$img----------\I|||
## |||          ||I|                  |I|||
## |||          ||I|    $pix          |I|||
## |||          ||I|                  |I|||
## |||          ||I\------------------/I|||
## |||          ||\====================/|||
## |||          ||                      |||
## |||          ||/-----$sbar----------\|||
## |||          |||                    ||||
## |||          ||\--------------------/|||
## |||          |||                    ||||
## |||          ||/-----$findb---------\|||
## |||          |||/-$findw-\/-$findl-\||||
## |||          ||||        ||        |||||
## |||          |||\--------/\--------/||||
## |||          ||\--------------------/|||
## ||\----------/\----------------------/||
## |\------------------------------------/|
## \--------------------------------------/
#

#
my $current_stack = 1;
my $num_stack     = 2;
my $context_id;

# player coltrol
my $player;
my $delay      = 1000;
my $delay_step =   50; # <<<
my $busy;
my $delay_on_np = -1; # <<<
my $delay_count;
my $splayer;
my $sdelay = 5;
my $tdelay = 120; # <<<
my $slider;
my $osder;
my @alps=(0.01366186122365, # exp(-4
	  0.02489353418393,
	  0.04535897664471,
	  0.08264944411079,
	  0.15059710595610,
	  0.27440581804701,
	  0.5);
my $kalps;
my $palps;
my $excbid;
my $Fin;
our $endpix;
our $pausepix;
our $playpix;
my $osdpix;
my $osdmsg;
my $osdmsgid;
my $osdtype=0;
my $osdf;

# keyevent status
my $keyin;
my $scm;
my $SCM_SHFT=1;
my $SCM_CTRL=2;
my $SCM_META=4;
my ($ctrl,$shft,$meta)=(0,0,0);
my $keyin_event = 0;
my $rep;
my $pproc_key_x;
my $kpe_busy;
#my $nke; # for nk-coms

# reader
my ($frtag,$frh,@frpids);
my ($rctag,$rch);
my ($ipctag,$ipch,$ipcspid);
my @fnbuf;

# button press events
my @apos;
my $isclick;
my $isdrag;
my ($ctrlm,$shftm,$metam)=(0,0,0); # for butpre

# commands
my $stacom;
my $ecom;
my $lastkc;
my @schists=("");
my $acctag;
my $khist=0;
my $mhist;

# key-event rec/player
my @recs;
my $crec;
my $nrec=12;

# keysim ques
my @ksqs;
my $wksqid=-1;
my $kscbid;

# find mode params
my $firstfound;
my $findt=0;    # 0=basename, 1=fullpath
my $findo="i";   # case insensitive
my $lastfind;

# misc
my $last_visit;
my @tmpdirs;
my $enc=find_encoding("UTF-8");
my $avr_aspect=0;
my $tmpmsg;
my $nfsorter;
my $smode=0;
my $gmode=0;
my $cmode=0;
my $dmode=0;
my $synclog=0;
my $sloop;
my @lastfng;
my $sbmode=0;
my @pids;

my @find_targs = (
    "Base Name",
    "Root Path"
    );

my @rot_type = (
    "none",
    "counterclockwise",
    "upsidedown",
    "clockwise"
    );

my %pshash =("h" => 1, "v" => 2);
my %invori =("h" => "v", "v" => "h");

my $uvuid=0;
my %uvars;
my %keybinds;
my %ufncs;

#$# my $HK_key_press_event_1;

our $psbarstr;
our $psbarstr_def;

my $gidqr=qr/^\d:(\d+)/;
my $didqr=qr/^(\d+):/;
my $owhqr=qr/^\d:\d+:(\d*):(\d*)/;

#-------------------------------------------------------------------

close STDIN; open(STDIN,"/dev/null");
local $|=1;
$SIG{TERM}=$SIG{INT}=$SIG{HUP}=\&delete_event;
&start; undef &start;
&echo("starting Gtk2->main");
Gtk2->main;
&echo("Gtk2->main exited");
exit;

#-------------------------------------------------------------------

sub spfp{
    (my $d=$_[0])=~s/\/[^\/]*$//;
    (my $f=$_[0])=~s/.*\///;
    return ($d,$f);
}
sub get_di{
    for(my $k=0;$k<=$#dirs; $k++){
	return $k if $dirs[$k] eq $_[0];
    }
    push @dirs, $_[0];
    return $#dirs;
}
sub get_fn{
    return if($_[0] < 0 || $_[0] > $#files);
    (my $f=$files[$_[0]])=~s/$didqr//;
    return $dirs[$1]."/".$f;
}
sub reg_file{
#    my ($d,$f,$pfs,$pss,$pgs,owh)=@_;
    my $pfs=$_[2];
    my $pss=$_[3];
    push @$pfs,$_[0].":$_[1]";
    my $g=$_[0]+1;
    &add_gids($_[4],$g);
    if($pss){
	my $s="0:$g:"
	    .(defined($_[5])?$_[5].":":"");
	push @$pss,$s;
    }
}
sub add_files{
    my $pss=$_[0]?\@stats:undef;
    foreach(@_[1..$#_]){
	next unless defined $_;
	if(substr($_,0,1) ne "/"){
	    require "Cwd.pm";
	    $_=Cwd::cwd()."/$_";
	}
	my ($d,$f)=&spfp($_);
	my $g=&get_di($d);
	&reg_file($g,$f,\@files,$pss);
	# this should not work on flist. no function to load flist
	# except at the startup where @baks is always empty.
	&reg_file($g,$f,$baks[0]->[1],$baks[0]->[2],$baks[0]->[3]) if @baks;
    }
}
sub add_gids{
#    my ($pss,@new_gids)=@_; # $gid must not be 0
    my $pgs=defined($_[0])?$_[0]:\@gids; # is sorted
    if(@$pgs){
	my @gs;
      GIN: foreach my $g (@_[1..$#_]){
	  foreach(@$pgs){
	      next GIN if $g==$_;
	      last if $g<$_;
	  }
	  push @gs, $g;
      }
	return unless @gs;
	push @$pgs, @gs;
    }else{
	@$pgs=@_[1..$#_];
    }
    @$pgs = sort {$a <=> $b} @$pgs;
}

# to be discarded
sub echo{
    my $m=shift;
    my $t;
    if($m=~s/^(#[^:]*:)//){
	$t=$1;
	if($t=~/^#(.*):/){
	    $t=$1;
	}
    }elsif(!$devm){
	return;
    }
    $m.=join(" ",@_);
    chomp($m);
    my $fh;
    given($t){
	when(["WARN","ERR"]){
	    $fh=*STDERR;
	    $m="\e[1;31m".$m."\e[0m";
	}
	default{
	    $fh=*STDOUT;
	}
    }
    say $fh $m;
}

sub runcom{
    # 0: com or ufnc
    # 1: args
    my $rm=0;
    $lastkc="$_[0]($keyin)" if(defined $keyin && length($keyin)>0 && !ref($_[0]));
    my @args=defined $_[1]?(
	$_[1] eq "r"?(
	    (defined $theA?$theA+1:$kf+1)
	    ."-"
	    .(defined $theB?$theB+1:$kf+1))
	:split(/,/,$_[1]))
	:split(/,/,$keyin);
    &echo("runcom: args: ".join(",",@args));
  ITEM: foreach(@args){
      &echo("runcom: $_[0]: $_");
      next unless defined $_;
      my ($a,$b,$s,$d);
      my $psetab=sub{
	  ($a,$b,$s,$d)=(0,0,"",0);
	  if($_[0]=~/(-*\d+)-(-*\d+)/){
	      $a=$1; $b=$2;
	      $a=@files+$a+1 if($a<0);
	      $b=@files+$b+1 if($b<0);
	      $d=$a<=$b?1:-1;
	  }elsif($_[0]=~/([.\d]+)/){
	      $a=$1; $b=$1;
	      $d=1;
	      if($_[0]=~/$a\+/){
		  $s="+";
	      }elsif($_[0]=~/$a-/){
		  $s="-";
	      }
	      if($_[0]=~/-$a/){
		  $a=-$a; $b=-$b;
	      }
	  }else{
	      return 0;
	  }
	  return 1;
      };
      next unless $psetab->($_);
      for(my $n=$a;($d>0&&$n<=$b||$d<0&&$n>=$b);$n+=$d){
	  my $psetv=sub {return $s eq "+"?$_[0]+$n:$s eq "-"?$_[0]-$n:$n;};
	  my $pseti=sub {
	      if($_[0]>=1&&$_[0]<=@files){
		  return $_[0]-1;
	      }elsif($_[0]<=-1&&$_[0]>=-@files){
		  return @files+$_[0];
	      }else{
		  return undef;
	      }
	  };
	  my $psetv2=sub{
	      my $x=$psetv->($_[0]);
	      $n=$a if(@args>=2&&$psetab->($args[1]));
	      return ($x,$psetv->($_[1]));
	  };

	  my $done=1;

	  if(ref($_[0])){
	      last unless $_[0]->($psetv,$pseti,$n,\$rm);
	      next;
	  }

	  given($_[0]){ # arb. number of args
	      when("Y"){
		  goto ERR unless defined $kf;
		  my $k=$pseti->($psetv->($kf+1));
		  $nselect++ if(defined $k && !substr($stats[$k],0,1,"1"));
	      }
	      when("D"){
		  goto ERR unless defined $kf;
		  my $k=$pseti->($psetv->($kf+1));
		  $nselect-- if(defined $k && substr($stats[$k],0,1,"0"));
	      }
	      when("y"){
		  goto ERR unless defined $kf;
		  my $k=$pseti->($psetv->($kf+1));
		  if(defined $k){
		      if(substr($stats[$k],0,1)){
			  substr($stats[$k],0,1,"0");
			  $nselect--;
		      }else{
			  substr($stats[$k],0,1,"1");
			  $nselect++;
		      }
		  }
	      }
	      when("k"){
		  goto ERR unless defined $kf;
		  my $k=$pseti->($psetv->($kf+1));
		  next unless defined $k;
		  $stats[$k]="D";
		  $rm=1
	      }
	      when("backslash"){
		  goto ERR unless defined $kf;
		  my $k=$pseti->($psetv->($kf+1));
		  &dopop($k);
	      }
	      when("d"){
		  goto ERR unless defined $kf;
		  if($meta){
		      foreach(@stats){
			  if($_=~$gidqr){
			      if($n==0||$n==$1){
				  substr($_,2,length($1),"0");
			      }
			  }
		      }
		      &TNV::set_ctid(undef,1) if $tnvmode>=0;
		  }else{
		      $done=0;
		  }
	      }
	      when("space"){
		  goto ERR unless defined $kf;
		  foreach(@stats){
		      if($_=~$gidqr){
			  if($n==0||$n==$1){
			      unless($shft){
				  $nselect++ unless substr($_,0,1,"1");
			      }else{
				  $nselect-- if substr($_,0,1,"0");
			      }
			  }
		      }
		  }
		  &TNV::set_ctid(undef,1) if $tnvmode>=0;
	      }
	      default{$done=0;}
	  }
	  next if $done;

	  goto FAILURE unless $a==$b;
	  given($_[0]){ # one argument command
	      when("g"){
		  goto ERR unless defined $kf;
		  my $k=$pseti->($psetv->($kf+1));
		  &show_image($k) if defined $k;
	      }
	      when("X"){
		  $psidx=$psetv->($psidx);
		  &set_ps;
	      }
	      when("x"){
		  my $s = $psetv->($scale);
		  if($s>0){
		      $scale=$s;
		      &resize("s");
		      &set_pix();
		  }
	      }

	      when ("w"){
		  goto ERR unless defined $kf;
		  &resize(undef,$psetv->($pix->get_width),$pix->get_height);
		  &set_pix();
	      }
	      when ("h"){
		  goto ERR unless defined $kf;
		  &resize(undef,$pix->get_width,$psetv->($pix->get_height));
		  &set_pix();
	      }
	      when ("s"){
		  goto ERR unless defined $kf;
		  &resize(undef,$psetv2->($pix->get_width,$pix->get_height));
		  &set_pix();
	      }
	      &change_view_size($psetv->($cvsw),          &get_vh) when ("W");
	      &change_view_size($cvsw,          $psetv->(&get_vh)) when ("H");
	      &change_view_size($psetv2->($cvsw,&get_vh)) when ("S");
	      $theA=$pseti->($psetv->($kf)) when("a");
	      $theB=$pseti->($psetv->($kf)) when("b");
	      when("l"){
		  goto ERR unless defined $kf;
		  my $k=$rot;
		  $lurd[$k]=$psetv->($lurd[$k]);
		  $lurd[$k]=0 if $lurd[$k]<0;
		  &do_trans();
	      }
	      when("u"){
		  goto ERR unless defined $kf;
		  my $k=1+($rot>=3?$rot-4:$rot);
		  $lurd[$k]=$psetv->($lurd[$k]);
		  $lurd[$k]=0 if $lurd[$k]<0;
		  &do_trans();
	      }
	      when("r"){
		  goto ERR unless defined $kf;
		  my $k=2+($rot>=2?$rot-4:$rot);
		  $lurd[$k]=$psetv->($lurd[$k]);
		  $lurd[$k]=0 if $lurd[$k]<0;
		  &do_trans();
	      }
	      when("d"){
		  goto ERR unless defined $kf;
		  my $k=3+($rot>=1?$rot-4:$rot);
		  $lurd[$k]=$psetv->($lurd[$k]);
		  $lurd[$k]=0 if $lurd[$k]<0;
		  &do_trans();
	      }
	      when("z"){
		  $dscale=$n>0?$n:$dscale;
	      }
	      when("R"){
		  if(!defined $crec && $n>=1 && $n <=$nrec){
		      $crec=-$n;
		      $recs[$n-1]="";
		  }
	      }
	      $sdelay=$n>0?$n:$sdelay when("T");

	      when("t"){
		  $delay=$n>0?$n:$delay;
		  if(defined $player){
		      &player_stop;
		      &player_run;
		  }else{
		      $tmpmsg="player delay: $delay";
		  }
	      }

	      $tdelay=$n>0?$n:$tdelay when("u");
	      $dpx=$n>0?$n:$dpx when("p");
	      $dpx_boost=$n>0?$n:$dpx when("P");
	      when("Return"){
		  goto ERR unless defined $kf;
		  if($n>=0){
		      my $g=$n?int($n):"0";
		      if($tnvmode>=0&&!$img->has_focus){ # <<<<
			  my ($k1,$k2,$l)=&TNV::tnv_fnc(2); # get_selected
			  if(defined $k2){
			      $k1 =$k2;
			      $k2+=$l;
			      ($k1,$k2)=($k2,$k1) if $k1>$k2;
			  }else{
			      $k2=$k1;
			  }
			  for($k1..$k2){
			      if($stats[$_]=~$gidqr){
				  substr($stats[$_],2,length($1),$g);
			      }
			  }
		      }else{
			  if($stats[$kf]=~$gidqr){
			      substr($stats[$kf],2,length($1),$g);
			  }
		      }
		      &TNV::set_ctid(undef,1) if $tnvmode>=0;
		      &add_gids(undef,$g) if $g;
		  }
	      }
	      when("o"){
		  &with_tnv(0,$n);
	      }
	      when("n"){
		  if($n>1){
		      $rep=$n;
		      $pproc_key_x=\&proc_key_n;
		  }
	      }
	      default{
		  goto FAILURE;
	      }
	  }
	  last ITEM;
      }
  }
    if($rm){
	for(my $k=$#files;$k>=0;$k--){
	    &do_rm_entry($k) if $stats[$k] eq "D";
	}
	&show_image($kf,1);
	&TNV::set_ctid(undef,1) if $tnvmode>=0;
    }
  ERR:
    return 1;

  FAILURE:
    $tmpmsg="Invalid com: $_[0]";
}

sub redo_com{
    my $pfw=$findw->has_focus?\$findw
	:$comw->has_focus?\$comw
	:($tnvmode<=0||!TNV::tnv_fnc(5))?($current_stack==1?\$img:\$logw)
	:undef;
    &open_com unless $comb->visible;
    $comw->set_text($_[0]); # will give comw kb focus
    Glib::Source->remove($acctag) if $acctag;
    $acctag=Glib::Timeout->add_seconds(3,\&close_com,1);
    &do_com();
    if($pfw){
	$$pfw->grab_focus;
    }elsif($tnvmode>0){
	&TNV::tnv_fnc(4,1); # grab_focus
    }
}

sub get_dpx{return $shft?$dpx*$dpx_boost:$dpx;}

sub proc_key_common{
    if(defined($keyin)){
	my $ok=1;
	given($_[0]){
	    when ( $Gtk2::Gdk::Keysyms{period} ){
		$keyin .= ".";
		$keyin_event=1;
	    }
	    when ( $Gtk2::Gdk::Keysyms{minus} ){
		$keyin .= "-";
		$keyin_event=1;
	    }
	    when ( $Gtk2::Gdk::Keysyms{plus} ){
		$keyin .= "+";
		$keyin_event=1;
	    }
	    when ($Gtk2::Gdk::Keysyms{BackSpace}){
		my $len=length($keyin)-1;
		if($len>=0){
		    $keyin=substr($keyin,0,$len);
		    $keyin_event = $len;
		}
	    }
	    default{
		my $v;
#		if(defined($nke)){
#		    $shft=substr($nke,0,1);
#		    $ctrl=substr($nke,1,1);
#		    $meta=substr($nke,2,1);
#		    $v   =substr($nke,3);
#		    undef $nke;
#		}else{
		    $v=$_[0];
#		}
		if($v>255||(my $c=pack("C",$v))!~/^[-\d.,+]/){
		    if(length($keyin)>0){
			$c=Gtk2::Gdk->keyval_name($v);
			&runcom($c);
		    }
		    return TRUE;
		}else{
		    $ok=0;
		}
	    }
	}
	return TRUE if $ok;
    }

    {
	my $ok=1;
	given($_[0]){
	    when ( $Gtk2::Gdk::Keysyms{Down} ){
		return FALSE if $cmode;
		given($scm){
		    when([0,1]){
			&scroll(&get_dpx,"v");
		    }
		    when([$SCM_META,$SCM_META|$SCM_SHFT]){
			&runcom("H",&get_dpx."+");
		    }
		    when($SCM_CTRL|$SCM_META){
			$dpx-- if $dpx>1;
		    }
		    default{
			$ok=0;
		    }
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{Up} ){
		return FALSE if $cmode;
		given($scm){
		    when([0,1]){
			&scroll(-&get_dpx,"v");
		    }
		    when([$SCM_META,$SCM_META|$SCM_SHFT]){
			&runcom("H",&get_dpx."-");
		    }
		    when($SCM_CTRL|$SCM_META){
			$dpx++;
		    }
		    default{
			$ok=0;
		    }
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{Left} ){
		return FALSE if $cmode;
		given($scm){
		    when([0,1]){
			&scroll(-&get_dpx,"h");
		    }
		    when([$SCM_META,$SCM_META|$SCM_SHFT]){
			&runcom("W",&get_dpx."-");
		    }
		    when($SCM_CTRL|$SCM_META){
			$dpx_boost-- if $dpx_boost>2;
		    }
		    default{
			$ok=0;
		    }
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{Right} ){
		return FALSE if $cmode;
		given($scm){
		    when([0,1]){
			&scroll(&get_dpx,"h");
		    }
		    when([$SCM_META,$SCM_META|$SCM_SHFT]){
			&runcom("W",&get_dpx."+");
		    }
		    when($SCM_CTRL|$SCM_META){
			$dpx_boost++;
		    }
		    default{
			$ok=0;
		    }
		}
	    }
	    default{$ok=0};
	}
	return TRUE if $ok;
    }

    if($ctrl){
	given($_[0]){
	    when($Gtk2::Gdk::Keysyms{j}){
		if($lastkc){
		    (my $c=$lastkc)=~s/\((.+)\)//;
		    $keyin=$1;
		    &runcom($c);
		}
	    }
	    when($Gtk2::Gdk::Keysyms{m}){
		&redo_com($schists[1]) if $schists[1];
	    }
	    when($Gtk2::Gdk::Keysyms{e}){
		&redo_com($ecom) if defined $ecom;
	    }
	    when ( $Gtk2::Gdk::Keysyms{o} ){
		&select_files("add");
	    }
	    when ( $Gtk2::Gdk::Keysyms{f} ){
		&select_files("add,dir");
	    }
	    when ( $Gtk2::Gdk::Keysyms{O} ){
		&select_files("rep");
	    }
	    when ( $Gtk2::Gdk::Keysyms{F} ){
		&select_files("rep,dir");
	    }
	    when ( $Gtk2::Gdk::Keysyms{z} ){
		$win->iconify;
	    }
	    when ( $Gtk2::Gdk::Keysyms{x} ){
		$win->set_decorated(!$win->get_decorated);
	    }
	    when([$Gtk2::Gdk::Keysyms{F1},
		  $Gtk2::Gdk::Keysyms{F2},
		  $Gtk2::Gdk::Keysyms{F3},
		  $Gtk2::Gdk::Keysyms{F4},
		  $Gtk2::Gdk::Keysyms{F5},
		  $Gtk2::Gdk::Keysyms{F6},
		  $Gtk2::Gdk::Keysyms{F7},
		  $Gtk2::Gdk::Keysyms{F8},
		  $Gtk2::Gdk::Keysyms{F9},
		  $Gtk2::Gdk::Keysyms{F10},
		  $Gtk2::Gdk::Keysyms{F11},
		  $Gtk2::Gdk::Keysyms{F12}]){
		my $k=$_[0]-$Gtk2::Gdk::Keysyms{F1};
		unless(defined($crec)){
		    $crec=-($k+1);
		    $recs[$k]="";
		}else{
		    undef $crec;
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{g} ){
		if($nselect==0){
		    &init_stats(1);
		}else{
		    &init_stats(0);
		}
	    }
	    when($Gtk2::Gdk::Keysyms{question}){
		require "$mydir/imv_ver.pm";
		&VER::show_about($win->get_position,
				 $win->get_size,
				 $enc->decode($win->get_style->font_desc->get_family),
				 "imv",
				 "Version $VERSION",
				 "Copyleft (!c) 2013-2014 Elseif Laboratory",
				 (glob("$pixdir/logo/*.png")));
	    }
	    when($Gtk2::Gdk::Keysyms{h}){
		require "$mydir/imv_man.pm";
		my $m=&MAN::show_kb_man($mydir);
		&echo("#ERR:$m") if defined $m;
	    }
	    default{return FALSE;}
	}
    }else{ # !$ctrl
	given ($_[0]){
	    when ( $Gtk2::Gdk::Keysyms{underscore} ){
		$fit="=" unless($fit=~s/=//||$fit=~s/^W/W=/);
		&show_image();
	    }
	    when ( $Gtk2::Gdk::Keysyms{E} ){
		$fit=$fit eq "W"?"":"W"; &show_image();
	    }
	    when ( $Gtk2::Gdk::Keysyms{X} ){
		$fit=$fit eq "f"?"":"f"; &show_image();
	    }
	    when ( $Gtk2::Gdk::Keysyms{W} ){
		$fit=$fit eq "fw"?"":"fw"; &show_image();
	    }
	    when ( $Gtk2::Gdk::Keysyms{H} ){
		$fit=$fit eq "fh"?"":"fh"; &show_image();
	    }
	    when ( $Gtk2::Gdk::Keysyms{semicolon} ){
		$page_scan = (abs($page_scan) % 2 + 1)*$page_scan/abs($page_scan);
	    }
	    when ( $Gtk2::Gdk::Keysyms{period} ){
		#lr/ud 01,11,10,00
		if($leftright==$updown){
		    $updown=$updown?0:1;
		}else{
		    $leftright=$leftright?0:1;
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{asterisk} ){
		$page_scan = -$page_scan
	    }
	    when ( $Gtk2::Gdk::Keysyms{A} ){
		$animate=!$animate;
		&show_image(undef) if ($animate != $set_ani)
	    }
	    when ( $Gtk2::Gdk::Keysyms{braceleft} ){
		if($tnvmode>0){
		    $thump->set_position($thump->get_position-$dpx);
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{braceright} ){
		if($tnvmode>0){
		    $thump->set_position($thump->get_position+$dpx);
		}
	    }
	    when ( [$Gtk2::Gdk::Keysyms{parenright},
		    $Gtk2::Gdk::Keysyms{parenleft}]){
		my $d=abs($sdelay);
		if($_[0]==$Gtk2::Gdk::Keysyms{parenright}){
		    $d++;
		}else{
		    $d--;
		    $d=1 if $d<=0;
		}
		$sdelay=$sdelay>0?$d:-$d;
		$tmpmsg="slide show delay: $d";
		if(defined $splayer){
		    &show_osdmsg($tmpmsg);
		    Glib::Source->remove(abs($splayer));
		    if($splayer>0){
			$splayer = Glib::Timeout->add_seconds($sdelay,\&next_image,2);
		    }else{
			$splayer = -Glib::Timeout->add_seconds($sdelay,\&next_image,-2); # <<<
		    }
		}
	    }
	    when ( [$Gtk2::Gdk::Keysyms{bracketright},
		    $Gtk2::Gdk::Keysyms{bracketleft}] ){
		if($_[0]==$Gtk2::Gdk::Keysyms{bracketright}){
		    $delay += $delay_step;
		}else{
		    $delay -= $delay_step;
		    $delay = $delay_step if $delay<=0;
		}
		if(defined $player){
		    &player_stop;
		    &player_run;
		}else{
		    $tmpmsg="player delay: $delay";
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{ampersand} ){
		$tdelay=-120 if $tdelay==0; # <<<
		$tdelay=-$tdelay;
	    }
	    when ( $Gtk2::Gdk::Keysyms{less} ){
		$psidx=($psidx<=1&&$psidx>=0)?-2:$psidx-1;
		&set_ps;
	    }
	    when ( $Gtk2::Gdk::Keysyms{greater} ){
		$psidx=($psidx<=-1&&$psidx>=-2)?1:$psidx+1;
		&set_ps;
	    }
	    when ( $Gtk2::Gdk::Keysyms{bar} ){
		my $wp;
		given($swin->get_placement){
		    $wp="top-right" when("top-left");
		    $wp="bottom-right" when("top-right");
		    $wp="bottom-left" when("bottom-right");
		    $wp="top-left" when("bottom-left");
		}
		$swin->set_placement($wp);
	    }
	    when ( [$Gtk2::Gdk::Keysyms{0},
		    $Gtk2::Gdk::Keysyms{1},
		    $Gtk2::Gdk::Keysyms{2},
		    $Gtk2::Gdk::Keysyms{3},
		    $Gtk2::Gdk::Keysyms{4},
		    $Gtk2::Gdk::Keysyms{5},
		    $Gtk2::Gdk::Keysyms{6},
		    $Gtk2::Gdk::Keysyms{7},
		    $Gtk2::Gdk::Keysyms{8},
		    $Gtk2::Gdk::Keysyms{9}] )
	    {
		$keyin .= ($_[0]-$Gtk2::Gdk::Keysyms{0});
		$keyin_event = 1;
	    }
	    when ( $Gtk2::Gdk::Keysyms{comma} ){
		$keyin .= $keyin?",":"";
		$keyin_event = 1;
	    }
	    when ( $Gtk2::Gdk::Keysyms{M} ){
		my $m=$maximized;
		$maximized?$win->unmaximize:$win->maximize;
		$maximized=!$maximized if $maximized==$m; # <<< ws failed
	    }
	    when ( $Gtk2::Gdk::Keysyms{F} ){
		my $a=$above;
		$win->set_keep_above($above?FALSE:TRUE);
		$above=!$above if $a==$above; # ws_event failed
	    }
	    when ( $Gtk2::Gdk::Keysyms{f} ){
		return FALSE if $meta;
		my $f=$fullscreen;
		$fullscreen?$win->unfullscreen:$win->fullscreen;
		$fullscreen=!$fullscreen if $fullscreen==$f; # <<< ws failed
	    }
	    when ( $Gtk2::Gdk::Keysyms{asciicircum} ){
		$keep_crop  =! $keep_crop;
	    }
	    when ( $Gtk2::Gdk::Keysyms{percent} ){
		$keep_scale =! $keep_scale;
	    }
	    when ( $Gtk2::Gdk::Keysyms{quotedbl} ){
		$keep_size  =! $keep_size;
	    }
	    when ( $Gtk2::Gdk::Keysyms{grave} ){
		$keep_orient   =! $keep_orient;
	    }
	    when ( $Gtk2::Gdk::Keysyms{numbersign} ){
		$keep_rot   =! $keep_rot;
	    }
	    when ( $Gtk2::Gdk::Keysyms{dollar} ){
		$keep_flips =! $keep_flips;
	    }
	    when ( $Gtk2::Gdk::Keysyms{at} ){
		$keep_pos   =! $keep_pos;
	    }
	    when ( $Gtk2::Gdk::Keysyms{J} ){
		$init_pos = !$init_pos;
	    }
	    when ( $Gtk2::Gdk::Keysyms{B} ){
		($theA,$theB)=($theB,$theA);
	    }
	    when ( $Gtk2::Gdk::Keysyms{a} ){
		$theA = (defined($theA)&&$theA==$kf)?undef:$kf;
	    }
	    when ( $Gtk2::Gdk::Keysyms{b} ){
		$theB = (defined($theB)&&$theB==$kf)?undef:$kf;
	    }
	    when ( $Gtk2::Gdk::Keysyms{question} ){
		require "$mydir/imv_help.pm";
		&HELP::show_help($mydir,$me,$defw,$defh,
				 $ecom,$schists[1],$lastkc,\@recs,
				 $win->get_icon_list);
	    }
	    when ( $Gtk2::Gdk::Keysyms{slash} ){
		unless($meta){
		    &open_find;
		}else{
		    $findt=!$findt;
		    &do_find if $findb->visible;
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{c} ){
		unless($meta){
		    undef $theA;
		    undef $theB;
		}else{
		    $findo=($findo?"":"i");
		    &do_find if $findb->visible;
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{i} ){
		unless($meta){
		    unless($splayer){
			my ($w,$h)=$win->get_size;
			my $bh=$sbar->allocation->height;
			if($sbar->visible){
			    $sbar->hide;
			    $h-=$bh;
			}else{
			    $h+=$bh;
			    $sbar->show;
			}
			$win->resize($w,$h);
		    }else{
			$osdtype=$osdtype==2?0:$osdtype+1;
			$dare->queue_draw;
		    }
		}else{
		    undef $psbarstr;
		    $psbarstr=$psbarstr_def if $psbarstr_def;
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{exclam} ){
		&open_com;
	    }
	    when([$Gtk2::Gdk::Keysyms{F1},
		  $Gtk2::Gdk::Keysyms{F2},
		  $Gtk2::Gdk::Keysyms{F3},
		  $Gtk2::Gdk::Keysyms{F4},
		  $Gtk2::Gdk::Keysyms{F5},
		  $Gtk2::Gdk::Keysyms{F6},
		  $Gtk2::Gdk::Keysyms{F7},
		  $Gtk2::Gdk::Keysyms{F8},
		  $Gtk2::Gdk::Keysyms{F9},
		  $Gtk2::Gdk::Keysyms{F10},
		  $Gtk2::Gdk::Keysyms{F11},
		  $Gtk2::Gdk::Keysyms{F12}]){
		my $k=$_[0]-$Gtk2::Gdk::Keysyms{F1};
		unless($shft){
		    if(defined $recs[$k]){
			&start_keysim($recs[$k]);
		    }else{
			$tmpmsg="F".($k+1)." undef";
		    }
		}else{
		    &mess("\nF".($k+1)." key-events:\n");
		    if(defined $recs[$k]){
			my $i=0;
			foreach(split(/:/,$recs[$k])){
			    $i++;
			    &mess("$i: $_\n");
			}
		    }else{
			&mess("undef\n");
		    }
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{I} ){
		$sbmode++;
	    }
	    when ( $Gtk2::Gdk::Keysyms{z} ){
		return FALSE unless $meta;
		$win->iconify;
		Glib::Timeout->add(1,sub{kill 'STOP', $$; $win->deiconify; return FALSE;});
	    }
	    when ( $Gtk2::Gdk::Keysyms{o} ){
		unless($meta){
		    &with_tnv(undef);
		}else{
		    &with_tnv(-1);
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{O} ){
		&with_tnv(1);
	    }
	    defalt{return FALSE;}
	}
    }
    return TRUE;
}

sub proc_key_img{
    return -1 unless defined $kf;

    if($cmode){
	my $ok=1;
	my $d=&get_dpx;
	given ($_[0]){
	    when ( $Gtk2::Gdk::Keysyms{Left} ){
		if($cmode==2){
		    &runcom("r","$d+");
		}else{
		    &runcom("l","$d-");
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{Up} ){
		if($cmode==2){
		    &runcom("d","$d+");
		}else{
		    &runcom("u","$d-");
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{Right} ){
		if($cmode==2){
		    &runcom("r","$d-");
		}else{
		    &runcom("l","$d+");2
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{Down} ){
		if($cmode==2){
		    &runcom("d","$d-");
		}else{
		    &runcom("u","$d+");
		}
	    }
	    default{$ok=0;}
	}
	return TRUE if $ok;
    }

    if($ctrl){
    	given($_[0]){
	    when ( $Gtk2::Gdk::Keysyms{Left} ){
		&runcom("w",&get_dpx."-");
	    }
	    when ( $Gtk2::Gdk::Keysyms{Up} ){
		&runcom("h",&get_dpx."-");
	    }
	    when ( $Gtk2::Gdk::Keysyms{Right} ){
		&runcom("w",&get_dpx."+");
	    }
	    when ( $Gtk2::Gdk::Keysyms{Down} ){
		&runcom("h",&get_dpx."+");
	    }

	    when ( $Gtk2::Gdk::Keysyms{Return} ){
		&next_group(1) if $gmode;
	    }
	    when ( $Gtk2::Gdk::Keysyms{BackSpace} ){
		&next_group(-1) if $gmode;
	    }
	    when ( $Gtk2::Gdk::Keysyms{k} ){
		if($nselect>0){
		    for(my $k=$#files;$k>=0;$k--){
			&do_rm_entry($k) if substr($stats[$k],0,1);
		    }
		    &show_image($kf);
		    &TNV::set_ctid(undef,1) if $tnvmode>=0;
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{w} ){
		&runcom("k","r");
		&TNV::set_ctid(undef,1) if $tnvmode>=0;
	    }
	    when ( $Gtk2::Gdk::Keysyms{K} ){
		&rm_all;
		&TNV::set_ctid(undef,1) if $tnvmode>=0;
	    }
	    when($Gtk2::Gdk::Keysyms{space}){
		unless($shft){
		    &toggle_gmode;
		}else{
		    &toggle_smode;
		}
	    }
	    when($Gtk2::Gdk::Keysyms{equal}){
		if($tnvmode>0){
		    my ($w,$h)=&TNV::tnv_fnc(3);
		    my $s=24+(($tnvmode % 2 == 1)?$w:$h+16); # <<<<<<<<
		    if($tnvmode==3){
			$s=($win->get_size)[0]-$s;
		    }elsif($tnvmode==4){
			$s=($win->get_size)[1]-$s;
		    }
		    $thump->set_position($s);
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{b} ){
		if(defined($theA)||defined($theB)){
		    for(my $k=(defined($theA)?$theA:0);
			$k<=(defined($theB)?$theB:$#files);$k++){
			$nselect++ unless(substr($stats[$k],0,1,"1"));
		    }
		}
		&TNV::set_ctid(undef,1) if $tnvmode>=0;
	    }
	    when($Gtk2::Gdk::Keysyms{t}){
		if($nselect>0&&$nselect<@files){
		    my @fs;
		    my @ss;
		    my @ts;
		    my $j=-1;
		    my $i=$nselect-1;
		    my $p;
		    for(my $k=0;$k<=$#files;$k++){
			if(substr($stats[$k],0,1)){
			    $p=++$j;
			}else{
			    $p=++$i;
			}
			$fs[$p]=$files[$k];
			$ss[$p]=$stats[$k];
			$ts[$p]=$thums[$k] if @thums;
		    }
		    @files=@fs;
		    @stats=@ss;
		    @thums=@ts if @thums;
		    &TNV::set_ctid(undef,1) if $tnvmode>=0;
		    &show_image($kf,1);
		    undef @fs;
		    undef @ss;
		    undef @ts;
		}
	    }
	    when ( [$Gtk2::Gdk::Keysyms{s},
		    $Gtk2::Gdk::Keysyms{S}]){
		my $f;
		if($shft){
		    $f=&select_files("save");
		}else{
		    my ($sec,$min,$hour,$mday,$month,$year,$wday,$stime)=localtime(time());
		    $f="imv_".($year+1900);
		    foreach($month+1,$mday,$hour,$min,$sec){
			$f.=sprintf("%.2d",$_);
		    }
		    $f.=".flist";
		}
		&save_fl($f,$nselect) if defined $f; # <<<<< save selected or all
	    }
 	    when ( $Gtk2::Gdk::Keysyms{c} ){
		$cmode=$cmode==1?2:1;
	    }
	    when($Gtk2::Gdk::Keysyms{p}){
		(defined($player) || &toggle_slide()) if defined $kf;
	    }
	    when ( $Gtk2::Gdk::Keysyms{a} ){
		foreach(@stats){
		    substr($_,0,1,"1");
		}
		$nselect=@files;
		&TNV::set_ctid(undef,1) if $tnvmode>=0;
	    }
	    when ( $Gtk2::Gdk::Keysyms{d} ){
		foreach(@stats){
		    substr($_,0,1,"0");
		}
		$nselect=0;
		&TNV::set_ctid(undef,1) if $tnvmode>=0;
	    }
	    when ( $Gtk2::Gdk::Keysyms{r} ){
		foreach(@stats){
		    $_=~s/^0/1/ || $_=~s/^1/0/;
		}
		$nselect=@files-$nselect;
		&TNV::set_ctid(undef,1) if $tnvmode>=0;
	    }
	    when ( $Gtk2::Gdk::Keysyms{backslash} ){
		for(0..$#stats){
		    &dopop($_) if substr($stats[$_],0,1);
		}
	    }
	    default{return FALSE;}
	}
    }else{ # !$ctrl
	given($_[0]){
	    when ( $Gtk2::Gdk::Keysyms{space} ){
		unless($splayer){
		    &scan;
		}else{
		    &toggle_splay();
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{Prior} ){
		&scroll("-P", "v");
	    }
	    when ( $Gtk2::Gdk::Keysyms{Next} ){
		&scroll("+P", "v");
	    }
	    when ( $Gtk2::Gdk::Keysyms{g} ){
		unless($meta){
		    my ($h,$v)=$current_stack==1?&get_orig(undef):(0,0);
		    &scroll("s","v",$v); &scroll("s","h",$h);
		}else{
		    &sort_f("g");
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{G} ){
		unless($meta){
		    my ($h,$v)=$current_stack==1?&get_orig(1):(0,-1);
		    &scroll("s","v",$v); &scroll("s","h",$h);
		}else{
		    &sort_f("g-");
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{f} ){
		return FALSE unless $meta;
		unless($dmode){
		    &find_dup;
		}else{
		    $dmode=0;
		    &pop_bak("D",1);
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{Y} ){
		unless(substr($stats[$kf],0,1,"1")){
		    $nselect++;
		    &TNV::set_ctid(undef) if $tnvmode>=0;
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{D} ){
		if(substr($stats[$kf],0,1,"0")){
		    $nselect--;
		    &TNV::set_ctid(undef) if $tnvmode>=0;
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{y} ){
		if(substr($stats[$kf],0,1)){
		    substr($stats[$kf],0,1,"0");
		    $nselect--;
		}else{
		    substr($stats[$kf],0,1,"1");
		    $nselect++;
		}
		&TNV::set_ctid(undef) if $tnvmode>=0;
	    }
	    when ( $Gtk2::Gdk::Keysyms{asciitilde} ){
		for(0..$#files){
		    if($files[$_]=~$didqr){
			my $d=$1+1;
			if($stats[$_]=~$gidqr){
			    substr($stats[$_],2,length($1),$d);
			    &add_gids(undef,$d);
			}
		    }
		}
		&TNV::set_ctid(undef,1) if $tnvmode>=0;
	    }
	    when ( $Gtk2::Gdk::Keysyms{t} ){
		&sort_f("t");
	    }
	    when ( $Gtk2::Gdk::Keysyms{T} ){
		&sort_f("t-");
	    }
	    when ( $Gtk2::Gdk::Keysyms{s} ){
		unless($meta){
		    &sort_f("s");
		}else{
		    &sort_f("x");
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{S} ){
		unless($meta){
		    &sort_f("s-");
		}else{
		    &sort_f("x-");
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{n} ){
		&sort_f("n");
	    }
	    when ( $Gtk2::Gdk::Keysyms{N} ){
		&sort_f("n-");
	    }
	    when ( $Gtk2::Gdk::Keysyms{z} ){
		&sort_f("z");
	    }
	    when ( $Gtk2::Gdk::Keysyms{Z} ){
		&sort_f("z-");
	    }
	    when ( $Gtk2::Gdk::Keysyms{U} ){
		&sort_f("R");
	    }
	    when($Gtk2::Gdk::Keysyms{V}){
		if(@files){
		    my $kf=$#files-$kf;
		    @files=reverse(@files);
		    @stats=reverse(@stats);
		    undef @thums;
		    &TNV::set_ctid($kf,1) if $tnvmode>=0;
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{P}){
		(defined($splayer) || &player_run || &player_stop) if defined $kf;
	    }
	    when ( $Gtk2::Gdk::Keysyms{e} ){
		&set_pix("W");
	    }
	    when ( $Gtk2::Gdk::Keysyms{x} ){
		&resize("f");
		&set_pix();
	    }
	    when ( $Gtk2::Gdk::Keysyms{w} ){
		&resize("fw");
		&set_pix();
	    }
	    when ( $Gtk2::Gdk::Keysyms{h} ){
		&resize("fh");
		&set_pix();
	    }
	    when ( $Gtk2::Gdk::Keysyms{p} ){
		if(defined($kf)){
		    undef $thums[$kf] if $thums[$kf];
		    &show_image($kf);
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{equal} ){
		$flipped=0;
		$mirrored=0;
		$rot=0;
		@lurd=(0,0,0,0);
		&resize(0);
		&set_pix();
	    }
	    when ( $Gtk2::Gdk::Keysyms{v} ){
		$flipped=!$flipped;
		&do_trans();
	    }
	    when ( $Gtk2::Gdk::Keysyms{r} ){
		$rot=$rot==3?0:$rot+1;
		&do_trans();
	    }
	    when ( $Gtk2::Gdk::Keysyms{R} ){
		$rot=$rot==0?3:$rot-1;
		&do_trans();
	    }
	    when ( $Gtk2::Gdk::Keysyms{m} ){
		$mirrored=!$mirrored;
		&do_trans();
	    }
	    when ( $Gtk2::Gdk::Keysyms{Delete} ){
		&do_rm_entry($kf);
		&show_image($kf);
		&TNV::set_ctid(undef,1) if $tnvmode>=0;
	    }
	    when ( $Gtk2::Gdk::Keysyms{d} ){
		unless($meta){
		    &mv_entry(1);
		    &TNV::set_ctid($kf,1) if $tnvmode>=0;
		}else{
		    foreach(@stats){
			if($_=~$gidqr){
			    substr($_,2,length($1),"0");
			}
		    }
		    &TNV::set_ctid(undef,1) if $tnvmode>=0;
		    &mess("Cleared all group id\n");
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{u} ){
		&mv_entry(-1);
		&TNV::set_ctid($kf,1) if $tnvmode>=0;
	    }
	    when ( $Gtk2::Gdk::Keysyms{plus} ){
		&resize($dscale);
		&set_pix();
	    }
	    when ( $Gtk2::Gdk::Keysyms{minus} ){
		&resize(-$dscale);
		&set_pix();
	    }
	    when ( [$Gtk2::Gdk::Keysyms{BackSpace},
		    $Gtk2::Gdk::Keysyms{k}] ){
		&next_image(-1);
	    }
	    when ( $Gtk2::Gdk::Keysyms{Return}){
		unless($shft){
		    &next_image(1);
		}else{
		    &next_image(-1);
		}
	    }
	    when ( $Gtk2::Gdk::Keysyms{j}){
		&next_image(1);
	    }
	    when ( $Gtk2::Gdk::Keysyms{C} ){
		&scroll("c","v"); &scroll("c","h");
	    }
	    when ( $Gtk2::Gdk::Keysyms{Home} ){
		&show_image(0);
	    }
	    when ( $Gtk2::Gdk::Keysyms{End} ){
		&show_image($#files);
	    }
	    when ( $Gtk2::Gdk::Keysyms{backslash} ){
		&dopop();
	    }
	    default{return FALSE;}
	}
    }
    return TRUE;
}

sub proc_key_logw{
    if($ctrl){
	given($_[0]){
	    when ( $Gtk2::Gdk::Keysyms{l} ){
                $log->set_text("");
            }
	    default{return FALSE;}
	}
    }else{ # !$ctrl
	given($_[0]){
	    when ( $Gtk2::Gdk::Keysyms{Return} ){
		&mess("");
	    }
	    when ( $Gtk2::Gdk::Keysyms{l} ){
		&print_flist(undef);
	    }
	    when ( $Gtk2::Gdk::Keysyms{backslash} ){
		$synclog=!$synclog;
		&mess("force sync is ".($synclog?"on":"off")."\n");
	    }
	    when ( $Gtk2::Gdk::Keysyms{L} ){
		&print_flist(1);
	    }
	    when ( $Gtk2::Gdk::Keysyms{d} ){
		&mess("\nimv internal settings:
scale factor increment   = $dscale
player delay increment   = $delay_step
delay on new page        = $delay_on_np
transition delay         = $tdelay
thumbnail aspect ratio   = ".sprintf("%.4f",$avr_aspect)."
minimum scroll size      = $screps
maximum window size      = $maxw"."x$maxh
dirs                     = ".(@dirs?"\n".join("\n",@dirs):"")."
gids                     = ".join(",",@gids)."
registered shell command = ".(defined($ecom)?$ecom:"")."
last shell command       = ".(defined($schists[1])?$schists[1]:"")."
last num-key-command     = ".(defined($lastkc)?$lastkc:"")."
pids                     = ".(@pids?join(",",@pids):"")."
IPC socket               = ".(defined($sockname)?$sockname:""));
	    }
	    default{return FALSE;}
	}
    }
    return TRUE;
}

sub proc_key{
# 0: key
# 1: shft
# 2: ctrl
# 3: meta
# 4: flag
#    1: keysim
#    2: tnv
    my $rc=FALSE; # -1 indicates processed but need to return FALSE by the caller
    return $rc unless defined $_[0];
    $keyin_event=0;
    $khist=abs($khist);
    ($shft,$ctrl,$meta)=($_[1],$_[2],$_[3]) if defined $_[1];
    $scm=$shft+2*$ctrl+4*$meta;
    my $for_keysim;
    my $from_tnvw;
    if(defined($_[4])){
	if($_[4]==1){
	    $for_keysim=1;
	}elsif($_[4]==2){
	    $from_tnvw=1;
	}
    }
    &echo("proc_key: $_[0] (s=$shft.c=$ctrl.a=$meta)");
    given($_[0]){
	when($Gtk2::Gdk::Keysyms{Escape}){
		unless($ctrl){
		    if($keyin){
			undef $keyin;
			&set_sbar;
		    }elsif(defined $player){
			&player_stop;
			&set_sbar;
		    }elsif(defined $splayer){
			&toggle_slide;
		    }elsif(@opened){
			if($findb->visible && $findw->has_focus){
			    &close_find;
			}elsif($comb->visible && $comw->has_focus){
			    &close_com();
			}else{ # close the last opend
			    for(my $k=0;$k<=$#opened;$k++){
				my $c=pop @opened;
				last if &$c;
			    }
			}
		    }elsif($cmode){
			$cmode=0;
			&set_sbar;
		    }else{
			if($current_stack==1){
			    $img->grab_focus;
			}else{
			    $logw->grab_focus;
			}
			if($rep){
			    $rep=-abs($rep);
			}
		    }
		}else{ # $ctrl
		    if($frtag){
			&smess("Canceling reading files...");
#			$win->window->process_all_updates; # <<<<<<<<<<<
			&end_files(1);
		    }elsif(defined $crec){
			undef $crec;
			&set_sbar;
		    }elsif($dmode){
			$dmode=0;
			&pop_bak("D",undef,1);
			&set_sbar;
		    }
		}
		goto FIN;
	}
	when($Gtk2::Gdk::Keysyms{Return}){
	    if($meta){
		&switch_stack();
		goto FIN;
	    }
	}
	when($Gtk2::Gdk::Keysyms{Tab}){
	    $recs[$crec].=":Tab" if defined $crec;
	    if($comb->visible && $comw->has_focus){
		&do_com(1);
		goto FIN;
	    }
	    $rc=-1;
	    goto EXIT; # <<<
	}
	when($Gtk2::Gdk::Keysyms{q}){
	    kill 'KILL', 0 if $ctrl;
	}
    }

    my $preck;
    if(defined $crec){
	$preck=sub{
	    if($_[0]>=32&&$_[0]<=127){
		$recs[$crec].=":=".pack("C",$_[0]);
	    }else{
		$recs[$crec].=":~".$_[0];
	    }
	}
    }

    # key from keysim will never be passed to $findw
    if($findb->visible && $findw->has_focus && !$meta && !$for_keysim){
	$preck->($_[0]) if defined($crec)&&$scm<=1; # CM- keys are not to be recorded
	if($ctrl){
	    if($_[0]==0xff52){ # ^Up
		$findw->set_text($lastfind) if $lastfind;
		goto FIN;
	    }elsif($_[0]==$Gtk2::Gdk::Keysyms{u}){
		$findw->set_text("");
		goto FIN;
	    }
	}
	$rc=-1;
	goto EXIT;
    }

    # key from keysim will never be passed to $comw except Return
    if($comb->visible && $comw->has_focus){
	if($_[0]==$Gtk2::Gdk::Keysyms{Return}){
	    &do_com();
	    goto FIN;
	}elsif(!$for_keysim){
	    $preck->($_[0]) if defined($crec)&&$scm<=1; # CM- keys are not to be recorded
	    $coml->set_text($rctag?"busy":"ready") if($_[0]>=32&&$_[0]<=127
				   ||$_[0]==0xff08||$_[0]==0xffff);
	    if($ctrl){
		if($_[0]==0xff52||$_[0]==0xff54){
		    &echo("khist=$khist,#=$#schists");
		    $khist=-$khist if $khist<0;
		    unless(defined($mhist)){
			$mhist=$comw->get_text;
			$mhist=~s/^\s//;
			$mhist=~s/\s$//;
		    }
		    my $kh0=$khist if length $mhist;
		    while(1){
			if($_[0]==0xff52){
			    $khist+=$#schists>$khist?1:0;
			}else{
			    $khist-=$khist>0?1:0;
			}
			last unless $mhist;
			last if $schists[$khist]=~/^\s*$mhist/;
			if($khist==0||$khist==$#schists){
			    $khist=$kh0 if defined $kh0;
			    last;
			}
		    }
		    $comw->set_text($schists[$khist]);
		    $comw->set_position(length($schists[$khist]));
		    $khist=-$khist;
		    goto FIN;
		}elsif($_[0]==$Gtk2::Gdk::Keysyms{c}){
		    &end_rcout() if $rctag;
		}elsif($_[0]==$Gtk2::Gdk::Keysyms{u}){
		    $comw->set_text("");
		    goto FIN;
		}
	    }
	}
	$rc=-1;
	goto EXIT;
    }

    return TRUE if ( !defined($keyin)
		     && $tnvmode>=0 && !$from_tnvw
		     && &TNV::proc_key($_[0],$shft,$ctrl,$meta,1));
# return FALSE if not focused

    if( $_[0] == $Gtk2::Gdk::Keysyms{q}||$_[0] == $Gtk2::Gdk::Keysyms{Q}){
	# must not proceed to set_sbar
	# quit even if in keyin event
	$win->destroy;
	return TRUE; # <<<
    }

    unless(&proc_key_common($_[0])){
	given($current_stack){
	    when(1){
		goto EXIT unless(&proc_key_img($_[0]));
	    }
	    when(2){
		goto EXIT unless(&proc_key_logw($_[0]));
	    }
	}
    }

    undef $keyin if(defined $keyin && !$keyin_event);
    &set_sbar;

  FIN:
    $rc=TRUE;
    if(defined $crec){
	if($crec>=0){
	    my $s=($shft?"S":"").($ctrl?"C":"").($meta?"M":"");
	    $s.="-" if $s ;
	    $recs[$crec].=($recs[$crec]?":":"").$s.Gtk2::Gdk->keyval_name($_[0]);
	}else{
	    $crec=-$crec-1;
	}
    }
    &echo("key ACCEPT");
  EXIT:
    if($khist>0){
	$khist=0;
	undef $mhist;
    }
    ($shft,$ctrl,$meta)=(0,0,0) if $for_keysim;
    return $rc;
}

sub dec_kb{
    return $_[0] unless $_[0]=~/\d+/;
    my $s=substr($_[0],0,1);
    my $c=substr($_[0],1,1);
    my $m=substr($_[0],2,1);
    my $v=substr($_[0],3);
    my $M=($s?"S":"").($c?"C":"").($m?"M":"");
    return ($M?"$M-":"").Gtk2::Gdk->keyval_name($v);
}

sub enc_kb{
    my ($s,$c,$m)=(0,0,0);
    my $k=$_[0];
    $k=~s/\s//g;
    if($k=~/([SCM]+)-.*/&&$1){
	my $x=$1;
	$s=1 if $x=~/S/;
	$c=1 if $x=~/C/;
	$m=1 if $x=~/M/;
	$k=~s/.*-//;
    }
    $s=1 if(length($k)==1&&$k=~/[A-Z]/);
    my $v=$Gtk2::Gdk::Keysyms{$k};
    unless(defined($v)){
	&mess("invalid keysym: $k\n","ERR");
	return undef;
    }
    return "$s$c$m$v";
}

sub set_keybind{
    # only a translation
    my ($from,$to)=split(/=>/,$_[0]);
    $from=&enc_kb($from);
    $to=&enc_kb($to);
    if(defined($from)&&defined($to)){
	$keybinds{$from}=$to;
    }else{
	&echo("#ERR:set_keybind failed: $_[0]");
    }
}

sub proc_key_1{return &proc_key($_[0]);}
sub proc_key_n{
    my $rc;
    for(my $i=1;$i<=$rep;$i++){
	$rc=&proc_key_1($_[0]);
	last unless $rc;
	last if $rep<0;
    }
    undef $rep;
    $pproc_key_x=\&proc_key_1;
    &set_sbar;
    return $rc;
}
sub key_press_event{
#    my ($w,$e) = @_;
#    return TRUE if defined $kscbid; # keysim in duty

#$#    if(defined($HK_key_press_event_1)){
#$#	unless(ref($HK_key_press_event_1)){
#$#	    $HK_key_press_event_1=eval "sub{$HK_key_press_event_1}";
#$#	    if($@){
#$#		&echo("#ERR: HK_key_press_event_1 failed:\n$@");
#$#		exit 1;
#$#	    }
#$#	}
#$#	my $rc=&$HK_key_press_event_1(@_);
#$#	return $rc if $rc >= 0;
#$#    }

    $kpe_busy=1;
    my $rc=FALSE;
    given($_[1]->keyval){
	when([$Gtk2::Gdk::Keysyms{Shift_L},
	      $Gtk2::Gdk::Keysyms{Shift_R},
	      $Gtk2::Gdk::Keysyms{Control_L},
	      $Gtk2::Gdk::Keysyms{Control_R},
	      $Gtk2::Gdk::Keysyms{Alt_L},
	      $Gtk2::Gdk::Keysyms{Alt_R}]){
	    $rc=TRUE;
	    goto EXIT;
	}
    }
    my $s=($_[1]->get_state >= ['shift-mask'])?1:0;
    my $c=($_[1]->get_state >= ['control-mask'])?1:0;
    my $m=($_[1]->get_state >= ['mod1-mask'])?1:0;
    &echo(sprintf("Key=%x, S=%d, C=%d, A=%d\n", $_[1]->keyval,$s,$c,$m));
    my $v=$_[1]->keyval;
    if(%keybinds||%ufncs){
	my $k="$s$c$m$v";
	if(exists($ufncs{$k})){
	    my $uf=$ufncs{$k}->[1];
	    if(($rc=$uf->())==-1&&defined($keyin)){
		&runcom($uf,undef);
		undef $keyin;
		$rc=TRUE;
	    }
	    &set_sbar; # <<<<<
	    goto EXIT;
	}
	if(!defined($keyin)&&defined(my $kb=$keybinds{$k})){
#	    $nke=$k;
	    $s=substr($kb,0,1);
	    $c=substr($kb,1,1);
	    $m=substr($kb,2,1);
	    $v=substr($kb,3);
	}
    }
    ($shft,$ctrl,$meta)=($s,$c,$m);
    $rc=&$pproc_key_x($v);
    &set_sbar if(defined $tmpmsg && !$tmpmsg && !$rc);
  EXIT:
    undef $kpe_busy;
    if($rc==FALSE){
	my $M=($s?"S":"").($c?"C":"").($m?"M":"");
	$tmpmsg="Unrecognized keybind: ".($M?"$M-":"").Gtk2::Gdk->keyval_name($v);
	&set_sbar;
    }
    $rc=FALSE if $rc==-1;
    return $rc;
}

sub do_com{
    # 0: do_filename_completion
    my $c=$comw->get_text;
    &mess("\n# $c\n");
    return TRUE unless $c;
    if(defined($_[0])){
	my $dir=(split(/\s/,$c,2))[0];
	$dir=($dir eq "cd")?1:0;
	my $p=$comw->get("cursor-position");
	return if $p==0;
	my $d=substr($c,0,$p);
	my $j=-1;
	my $jq=-1;
	my $q;
	for(my $k=$p-1;$k>=0;$k--){
	    my $c=substr($d,$k,1);
	    given($c){
		when(" "){
		    $j=$k+1 if $j<0;
		}
		when(["'",'"']){
		    if($jq>0){
			undef $q;
			$jq=0;
			last if $j>0;
		    }
		    $q=$c;
		    $jq=$k;
		}
	    }
	}
	$j=0 if $j<0;
	$j=$jq+1 if $q;
	my $ct=substr($c,$j,$p-$j);
	my @fs=glob("\"$ct*\"");
	return unless @fs;
	if($dir){
	    my @ffs;
	    foreach(@fs){
		push @ffs, $_ if -d $_;
	    }
	    return unless @ffs;
	    @fs=@ffs;
	}
	my $mk;
	my $lct=length($ct);
	if(@fs>1){
	    my $k1;
	    my $k2=4095; #<<<<<<<<<<<<<<<<<
	    foreach(@fs){
		$_=$enc->decode($_);
		$k2=$k1 if ($k1=length($_))<$k2;
	    }
	    $k1=$lct;
	    $mk=$k2-1;
	    if($k2>$k1){
	      WORDS: for(my $k=$k1;$k<$k2;$k++){
		  my $w=substr($fs[0],$k,1);
		  foreach(@fs[1..$#fs]){
		      if(substr($_,$k,1) ne $w){
			  $mk=$k-1;
			  last WORDS;
		      }
		  }
	      }
	    }
	}else{
	    $fs[0]=$enc->decode($fs[0]);
	    $fs[0].="/" if -d $fs[0];
	    $fs[0].=$q if $q;
	    $mk=length($fs[0])-1;
	}
	&mess("\n=> $d\n".join("\n",@fs)."\n") if @fs>1;
	if($mk>=$lct){
	    my $len;
	    if(substr($ct,0,1) ne "~"){
		$len=$mk-$lct+1;
		substr($c,0,$p).=substr($fs[0],$lct,$len);
	    }else{
		$len=$mk+1;
		$c=substr($c,0,$p-$lct).substr($fs[0],0,$len);
	    }
	    $comw->set_text($c);
	    $comw->set_position($p+$len);
	}
	return;
    }

    $c=~s/^\s*//;
    $c=~s/\s*$//;

    if($#schists==0||$schists[1] ne $c){
	$schists[0]=$c;
	unshift @schists,"";
    }
    $ecom=$c if $meta;
    if($rctag){
	$coml->set_text("busy");
	return;
    }
    #
    my $pgbn=sub{
	return unless $_[0];
	my $p=$_[0];
	my ($f,$s);
	$s=$1 if(($f=$_[1])=~s/(.*)\t//);
	for(my $k=0;$k<length($p);$k++){
	    given(substr($p,$k,1)){
		$f=~s/\/[^\/]*$// when("h");
		$f=~s/.*\///      when("t");
		$f=~s/\.[^.]*$//  when("r");
		$f=~s/.*\.//      when("e");
		$f.="\\n"         when("n");
		when("q"){
		    unless(defined $s){
			$f='"'.$f.'"';
		    }else{
			$f.='"';
			$s='"'.$s;
		    }
		}
	    }
	}
	$f="$s\t".$f if defined $s;
	$_[1]=$f;
    };
    $c=~s/\\%/\x01/g;
    $c=~s/\\#/\x02/g;

    my $sq=0;
    my $dq=0;
    for(my $i=0;$i<length($c);$i++){
	given(substr($c,$i,1)){
	    when('"'){
		$dq=$dq?0:1 unless $sq;
	    }
	    when("'"){
		$sq=$sq?0:1 unless $dq;
	    }
	    when("%"){
		substr($c,$i,1,"\x01") if $sq;
	    }
	    when("#"){
		substr($c,$i,1,"\x02") if $sq;
	    }
	    when(" "){
		substr($c,$i,1,"\x03") if $sq||$dq;
	    }
	}
    }

    my $p=qr/(:[htreqn]{1,1})*/;
    my $psub=sub{
	#my ($psym,$pre,$sin,$k1,$k2,$pisok,$pset)=@_;
	while($_[2]=~/$_[0](_?)($_[1])($p)/){
	    my $s;
	    my $k1=defined($_[3])?$_[3]:$2;
	    my $k2=defined($_[4])?$_[4]:$2;
	    my $m=$1;
	    for($k1..$k2){
		next unless $_[5]->($_);
		$pgbn->($3,my $t=$_[6]->($_));
		$s.=(defined($s)&&$m?" ":"").$t;
	    }
	    if(defined $s){
		$s=~s/%/\x01/g;
		$s=~s/#/\x02/g;
	    }else{
		$s="";
	    }
	    $_[2]=~s/$_[0]$m$_[1]$p/$s/;
	}
    };

    $psub->("%","f",$c,$kf,$kf,sub{return 1;},\&get_fn);
    $psub->("%","s",$c,0,$#files,sub{return substr($stats[$_[0]],0,1);},\&get_fn);
    $psub->("%","u",$c,0,$#files,sub{return !substr($stats[$_[0]],0,1);},\&get_fn);
    $psub->("%","a",$c,0,$#files,sub{return 1;},\&get_fn);
    $psub->("%","[0-9]+",$c,undef,undef,sub{return 1;},\&get_fn);

    my $pgetfr=sub{
	return $stats[$_[0]]."\t".&get_fn($_[0]);
    };
    $psub->("#","f",$c,$kf,$kf,sub{return 1;},$pgetfr);
    $psub->("#","s",$c,0,$#files,sub{return substr($stats[$_[0]],0,1);},$pgetfr);
    $psub->("#","u",$c,0,$#files,sub{return !substr($stats[$_[0]],0,1);},$pgetfr);
    $psub->("#","a",$c,0,$#files,sub{return 1;},$pgetfr);
    $psub->("#","[0-9]+",$c,undef,undef,sub{return 1;},$pgetfr);

    $c=~s/\x01/%/g;
    $c=~s/\x02/#/g;

    my $get_args=sub{
	return unless defined $_[0];
	if($_[1]){ # split
	    my @as=split(/\s/,$_[0],$_[1]);
	    foreach(@as){
		$_=~s/\x03/ /g;
		$_=~s/^["']?//;
		$_=~s/["']?$//;
	    }
	    return @as;
	}else{
	    my $a=$_[0];
	    $a=~s/\x03/ /g;
	    return $a;
	}
    };

    my $rc=0;
    my @sc=split(/\s/,$c,2);

    if(exists($ufncs{$sc[0]})){
	my $uf=$ufncs{$sc[0]}->[1];
	$rc=$uf->($sc[0],$sc[1]); # 1 on success
	$rc=!$rc; # 0 on success
    }else{
	given($sc[0]){
	    when("cd"){
		$c=($get_args->($sc[1],2))[0];
		$c=$ENV{"HOME"} unless $c;
		&mess("=> cd $c\n");
		$rc=chdir($c);
		&mess("cd failed: $! $?\n","ERR") unless $rc; #<<<<<<
		$rc=!$rc;
	    }
	    when("cle"){
		$#schists=0;
		$rc=0;
	    }
	    when(["load","run"]){
		if(defined($sc[1])){
		    my @args=$get_args->($sc[1],2);
		    @args=($args[0],undef,2," ",$args[1]) if $sc[0] eq "run";
		    $rc=!&set_ufnc(@args);
		}else{
		    &mess("too few arguments\n","ERR");
		    $rc=-1;
		}
	    }
	    when("eval"){
		$rc=-1 unless defined &eval_str($get_args->($sc[1]));
	    }
	    when(/^add/ || /^open/){
		if(defined($sc[1])){
		    my @fs=$get_args->($sc[1],1000);
		    &start_load($sc[0],@fs) if @fs;
		}else{
		    &mess("too few arguments\n","ERR");
		    $rc=-1;
		}
	    }
	    when("disconnect"){
		&end_ipc;
	    }
	    default{
		$c=$enc->encode($get_args->($c));
		$rc=&add_watcher(\&read_rcout,$c);
	    }
	}
    }
    if($rc){
	$coml->set_text("Error: ".(defined($rc)?$rc:"undef"));
    }else{
	$comw->set_text("") unless $acctag;
	$coml->set_text("OK");
    }
}

sub end_rcout{
    Gtk2::Helper->remove_watch($rctag);
    close($rch);
    my $rc=$?;
    $coml->set_text("RC = ".$rc/256) if $rc;
    &mess("$schists[1] exited with ".($rc/256)."\n",$rc?"ERR":"OK");
    return if $_[0];
    if($rc&&!$comw->get_text){
	$comw->set_text($schists[1]);
	$comw->set_position(length($schists[1]));
	# and clear from the history?
    }
    undef $rctag; undef $rch;
    $win->window->set_cursor(undef);
}

sub read_rcout{
    my $pid;
    ($rctag,$rch,$pid) = @_;
    my @ms=&get_lines($rch);
    unless(@ms){
	&end_rcout();
	&clean_pids($pid);
	return FALSE;
    }
    foreach(@ms){
	&echo($_);
	my $m=$enc->decode($_);
	&mess("$m\n");
	&echo("#MSG:$m\n") if defined $mystdout;
    }
    return TRUE;
}

sub open_com{
    if($comb->visible){
	$comw->grab_focus;
	return;
    }
    $comb->show;
    $win->set_focus($comw);
    $comw->set_text("");
    $coml->set_text("ready");
    push @opened, \&close_com;
    if($fit=~/^f/){
	&resize($fit);
	&set_pix();
    }
}

sub close_com{
    #0: try
    return unless $comb->visible; # <<<<< may leave $acctag defined
    if($rctag){
	unless($_[0]){
	    &end_rcout() if $rctag;
	}else{
	    return TRUE;
	}
    }
    if($_[0]){
	undef $acctag;
	return FALSE if $comw->has_focus;
    }
    $comb->hide;
    if($fit=~/^f/){
	&resize($fit);
	&set_pix();
    }
    return $_[0]?FALSE:1;
}

sub set_owh{
    for(0..$#stats){
	next if($stats[$_]=~$owhqr&&$1&&$2);
	my ($w,$h)=(Gtk2::Gdk::Pixbuf->get_file_info(&get_fn($_)))[1,2];
	next unless $w&&$h;
	my ($s,$g,$zw,$zh,$r)=split(/:/,$stats[$_],5);
	$r=defined($r)?$r:"";
	$stats[$_]=join(":",$s,$g,$w,$h,$r);
    }
}

sub find_dup{
    return unless @files;
    return if $dmode;
    &push_bak("D");
    $dmode=1;
    for(0..$#files){
	substr($stats[$_],2,length($1),0) if $stats[$_]=~$gidqr;
    }
    &set_owh;
    my $tdc=0;
    my @ss=&sort_f("s",1,1);
    undef @gids;
    for(my $k=0;$k<=$#files;$k++){
	next if $stats[$k]=~$gidqr&&$1;
	my ($wk,$hk)=$stats[$k]=~$owhqr;
	next unless $wk&&$hk;
	my $fk=&get_fn($k);
	next unless -f $fk; # reject symlinks
	my ($dk,$ik,$mdk);
	my $dc=0;
	for(my $j=$k+1;$j<=$#files;$j++){
	    last unless $ss[$k]==$ss[$j];
	    next if $stats[$j]=~$gidqr&&$1;
	    my ($wj,$hj)=$stats[$j]=~$owhqr;
	    next unless $wj&&$hk;
	    if($wk==$wj&&$hk==$hj){
		# file size and image size matched
		my $fj=&get_fn($j);
		if(-f $fj){
		    # and are not symbolic links
		    ($dk,$ik)=(stat($fk))[0,1] unless $dk;
		    my ($dj,$ij)=(stat($fj))[0,1];
		    if($dk!=$dj||$ik!=$ij){
			# not hard links
			require "Digest/MD5.pm"; # <<<<<<<<<<<<<<<<<<<
			unless($mdk){
			    open F, $fk;
			    binmode F;
			    $mdk=Digest::MD5->new->addfile(*F)->hexdigest;
			    close F;
			}
			open F, $fj;
			binmode F;
			my $mdj=Digest::MD5->new->addfile(*F)->hexdigest;
			close F;
			if($mdk eq $mdj){
			    $dc++;
			    substr($stats[$j],2,length($1),$k+1) if $stats[$j]=~$gidqr;
			}
		    }
		}
	    }
	}
	if($dc>0){
	    substr($stats[$k],2,length($1),$k+1) if $stats[$k]=~$gidqr;
	    &add_gids(undef,$k+1);
	}
	$tdc+=$dc;
    }
    if($tdc){
	my $j=-1;
	for(my $k=0;$k<=$#files;$k++){
	    if($stats[$k]=~$gidqr&&$1!=0){
		$j++;
		$stats[$j]=$stats[$k];
		$files[$j]=$files[$k];
	    }
	}
	$#stats=$#files=$j;
	undef @thums;
	&load_stat(0);
	$kf=0; # supress save_stat which would try to save stats before find
	&show_image(0);
	&TNV::set_ctid(0,1) if $tnvmode>=0;
	&show_image(0,1);
    }else{
	&pop_bak("D",undef,1);
	$dmode=0;
    }
    $tmpmsg=($tdc?$tdc:"No")." duplicates found";
}

sub do_find{
    my $f = $findw->get_text;
    # leading and trailing spaces are significant
    my $lbl=$find_targs[$findt].($findo?"/$findo":"");
    $findl->set_text("Find by ".$lbl);
    return if $f eq "";
    $lastfind=$f;
    my @ff;
    my @fs;
    eval{
	my $e = qr/$f/;
    };
    return if $@;
    my $e;
    given($findo){
	when(""){
	    $e=qr/$f/;
	}
	when("i"){
	    $e=qr/$f/i;
    	}
    }
    my $k0;
    my ($pfs,$pss)=(&get_bak("F"))[0,1];
    my $ns=0;
    if($findt==0){
	for(my $k=0;$k<=$#$pfs;$k++){
	    if($pfs->[$k]=~$e){
		$ns++ if substr($pss->[$k],0,1);
		push @ff, $pfs->[$k];
		push @fs, $pss->[$k];
		$k0=$k unless defined $k0;
	    }
	}
    }else{
	for(my $j=0;$j<=$#dirs;$j++){
	    if($dirs[$j]=~$e){
		my $d=qr/^$j:/;
		for(my $k=0;$k<=$#$pfs;$k++){
		    if($pfs->[$k]=~$d){
			$ns++ if substr($pss->[$k],0,1);
			push @ff, $pfs->[$k];
			push @fs, $pss->[$k];
			$k0=$k unless defined $k0;
		    }
		}
	    }
	}
    }
    $findl->set_text((@ff)." matches ".$lbl);
    if(@ff){
	$nselect=$ns;
	@files = @ff;
	@stats = @fs;
	unless($firstfound==$k0){
	    &load_stat(0);
	    $kf=0; # supress save_stat which would try to save stats before find
	    &show_image(0);
	    $firstfound=$k0;
	}
    }else{
	$firstfound=-1;
	&no_image;
    }
    if($tnvmode>0){
	undef @thums;
	&TNV::set_ctid(0,1);
    }
    &set_sbar;
}
sub push_bak{
    #0: tag
    return unless @files; # <<<<
    &save_stat($kf);
    my @fs=@files;
    my @ss=@stats;
    my @gs=@gids;
    my $ns=$nselect;
    my $k=$kf;
    push @baks, [$_[0],\@fs,\@ss,\@gs,\$k,\$ns];
}
sub get_bak{
    return unless @baks;
    for(my $k=$#baks;$k>=0;$k--){
	if($baks[$k]->[0] eq $_[0]){
	    return ($baks[$k]->[1],$baks[$k]->[2],$baks[$k]->[3],$baks[$k]->[4],$baks[$k]->[5]);
	}
    }
}
sub pop_bak{
    #0: ID
    #1: restore_display
    #2: ignore_changes
    return unless @baks;
    foreach(@baks){
	if($baks[$#baks]->[0] ne $_[0]){
	    # clears all the upper stacks
	    given($baks[$#baks]->[0]){
		when("F"){
		    &close_find;
		}
		when("D"){
		    $dmode=0;
		    &pop_bak("D",undef,1);
		}
		when("G"){
		    $gmode=0;
		    &pop_bak("G",1);
		}
		when("S"){
		    $smode=0;
		    &pop_bak("S",1);
		}
	    }
	}else{
	    last;
	}
    }
    my $p = pop @baks;
    my ($tag,$pfs,$pss,$pgs,$pkf,$pns) = ($p->[0],$p->[1],$p->[2],$p->[3],$p->[4],$p->[5]);
    my $tokf;
    unless($_[2]){
	&save_stat($kf) if defined $kf;
	for(my $k=0;$k<=$#stats;$k++){
	    for(my $j=0;$j<$#$pfs;$j++){
		if($pfs->[$j] eq $files[$k]){
		    my $s=substr($stats[$k],0,1);
		    $$pns+=$s?1:-1 if $s!=substr($pss->[$j],0,1);
		    $pss->[$j]=$stats[$k];
		    $tokf=$j if defined($kf)&&$k==$kf;
		    last;
		}
	    }
	}
        &add_gids(undef,@$pgs);
    }else{
        @gids=@$pgs;
    }
    $nselect=$$pns;
    @stats=@$pss;
    @files=@$pfs;
    if($_[1]){
	$tokf=defined($tokf)?$tokf:$$pkf;
	&load_stat($tokf);
	undef @thums;
	&show_image($tokf,1); # <<< better not call unless necessary
	&TNV::set_ctid($tokf,1) if $tnvmode>=0;
    }else{
	&TNV::set_ctid(undef,1) if $tnvmode>=0;
    }
    undef @$pss;
    undef @$pfs;
    undef @$pgs;
    undef $$pns;
    undef $$pkf;
    &set_sbar;
    return $tokf;
}

sub open_find{
    return if $frtag;
    return unless @files;
    if($fwcbid){
	$findw->grab_focus;
	return;
    }
    $firstfound=-1;
    $findw->set_text("");
    $findl->set_text("Find by ".$find_targs[$findt].($findo?"/$findo":""));
    &push_bak("F");
    push @opened, \&close_find;
    $fwcbid=$findw->signal_connect(changed => \&do_find);
    $findb->show;
    $win->set_focus($findw);
    if($fit=~/^f/){
	&resize($fit);
	&set_pix();
    }
}
sub close_find{
    return if $frtag;
    return unless defined $fwcbid;
    $findw->signal_handler_disconnect($fwcbid);
    undef $fwcbid;
    $findb->hide;
    &pop_bak("F",1);
    return 1;
}

sub next_group{
    my $k=-1;
    my $g=$gmode;
    my ($pfs,$pss,$pns)=(&get_bak("G"))[0,1,4];
    my $jg;
    my $fn;
    &save_stat($kf);
    for(my $j=0;$j<=$#gids;$j++){
	if($gids[$j]==$g){
	    $lastfng[$j]=$files[$kf];
	    last;
	}
    }
    foreach(@gids){
	if($_[0]>0){
	    for(my $j=0;$j<=$#gids;$j++){
		if($g<$gids[$j]){
		    $g=$gids[$j];
		    $jg=$j;
		    last;
		}
	    }
	    if($g==$gmode){
		$g=$gids[0];
		$jg=0;
	    }
	}else{
	    for(my $j=$#gids;$j>=0;$j--){
		if($g>$gids[$j]){
		    $g=$gids[$j];
		    $jg=$j;
		    last;
		}
	    }
	    if($g==$gmode){
		$g=$gids[$#gids];
		$jg=$#gids;
	    }
	}

	my $ok=0;
	foreach(@$pss){
	    if($_=~$gidqr&&$1==$g){
		$ok=1;
		last;
	    }
	}

	unless($ok){
	    if($g==$gids[$#gids]&&$_[0]>0){
		$g=$gids[0];
	    }elsif($g==$gids[0]&&$_[0]<0){
		$g=$gids[$#gids];
	    }
	    splice @gids, $jg, 1;
	    next;
	}

	$fn=$lastfng[$jg];
	for(my $k=0;$k<=$#files;$k++){
	    for(my $j=0;$j<=$#$pfs;$j++){
		if($files[$k] eq $$pfs[$j]){
		    my $s=substr($stats[$k],0,1);
		    $$pns+=$s?1:-1 if $s!=substr($$pss[$j],0,1);
		    $$pss[$j]=$stats[$k];
		    last;
		}
	    }
	}
	my $ns=0;
	for(0..$#$pfs){
	    if($$pss[$_]=~$gidqr&&$1==$g){
		$k++;
		$files[$k]=$$pfs[$_];
		$stats[$k]=$$pss[$_];
		$ns++ if substr($$pss[$_],0,1);
	    }
	}
	$nselect=$ns;
	last;
    }
    if($k>=0){
	$#files=$k;
	$#stats=$k;
	undef @thums;
	my $kn=0;
	if(defined $fn){
	    for(my $k=0;$k<$#files;$k++){
		if($files[$k] eq $fn){
		    $kn=$k;
		    last;
		}
	    }
	}
	undef $kf;
	$gmode=$g;
	&show_image($kn); # will load $gstats for $gmode
	&TNV::set_ctid(0,1) if $tnvmode>=0;
    }else{
	&echo("#ERR:INTERNAL ERROR: next_group: g=$g");
    }
}

sub toggle_smode{
    return if $frtag;
    if($smode){
	$smode=0;
	&pop_bak("S",1);
    }else{
	return unless $nselect;
	&push_bak("S");
	my $k=-1;
	my $tokf=0;
	for(0..$#files){
	    if(substr($stats[$_],0,1)){
		$k++;
		$files[$k]=$files[$_];
		$stats[$k]=$stats[$_];
		$tokf=$k if $_ == $kf;
	    }
	}
	undef @thums;
	$nselect=$k+1;
	$#files=$k;
	$#stats=$k;
	&load_stat($tokf);
	$kf=$tokf;
	&show_image($tokf);
	&TNV::set_ctid($tokf,1) if $tnvmode>=0;
	$smode=$nselect;
    }
}

sub toggle_gmode{
    return if $frtag;
    if($gmode){
	$gmode=0;
	undef @lastfng;
	undef @gstats;
	&pop_bak("G",1);
    }else{
	return if(!@files||!defined($kf));
	my ($gid)=$stats[$kf]=~$gidqr;
	my $rl=0;
	unless($gid){
	    foreach(@stats){
		($gid)=$_=~$gidqr;
		last if $gid;
	    }
	    return unless $gid;
	    $rl=1;
	}

	&push_bak("G");
	my $k=-1;
	my $tokf=0;
	my $ns=0;
	for(0..$#files){
	    if($stats[$_]=~$gidqr&&$1==$gid){
		$k++;
		$ns++ if substr($stats[$_],0,1);
		$files[$k]=$files[$_];
		$stats[$k]=$stats[$_];
		$tokf=$k if $_==$kf;
	    }
	}
	$nselect=$ns;
	$#files=$k;
	$#stats=$k;
	undef @thums;
	&load_stat($tokf);
	$kf=$tokf;
	&show_image($tokf) if $rl;
	&TNV::set_ctid($tokf,1) if $tnvmode>=0;
	$gmode=$gid;
    }
}

sub get_vh{
    my $h=$cvsh;
    return $h if defined $splayer;
    $h-=$sbar->allocation->height if $sbar->visible;
    $h-=$comb->allocation->height if $comb->visible;
    $h-=$findb->allocation->height if $findb->visible;
    return $h;
}

sub config_cb{
    # my ($w,$e,$tnvw,$tnvh)=@_;
    my ($cvsw_old,$cvsh_old)=($cvsw,$cvsh);
    ($cvsw,$cvsh)=$win->get_size; # $vbox->get_width etc are unreliable
    &echo("config_cb: in: $cvsw, $cvsh: ".(!defined($_[0])?"$_[2], $_[3]":"$_[0]"));
    ($tnvw,$tnvh)=($_[2],$_[3]) unless defined $_[0];
    if($tnvmode>0&&$tnvw>0&&$tnvh>0){
	if($tnvmode % 2 == 1){
	    $cvsw-=$tnvw+$tphs;
	}elsif($tnvmode % 2 == 0){
	    $cvsh-=$tnvh+$tphs;
	}
    }
    unless(defined $dare){
	$cvsw-=$swin->style->xthickness*2;
	$cvsh-=$swin->style->ythickness*2;
    }

    return if ($cvsw_old==$cvsw && $cvsh_old==$cvsh);
    &echo("config_cb: out: $cvsw, $cvsh");

    &set_ps unless defined $dare;
    &show_image(undef,1) if $fit =~ /^f/;
    #
    return FALSE;
}

sub set_gsbg{
    my ($dw,$dh)=@_;
    my ($x0,$y0,$x1,$y1);
    my $d=$sbg;
    if($d=~s/^h://){
	($x0,$y0,$x1,$y1)=(0,0,$dw-1,0);
    }elsif($d=~s/^d://){
	($x0,$y0,$x1,$y1)=(0,0,$dw-1,$dh-1);
    }elsif($d=~s/^r://){
	($x0,$y0,$x1,$y1)=($dw-1,0,0,$dh-1);
    }else{ # /^v:/
	$d=~s/^v://;
	($x0,$y0,$x1,$y1)=(0,0,0,$dh-1);
    }
    $d=~s/rainbow/violet,navy,blue,green,yellow,orange,red/i;
    $gsbg=Cairo::LinearGradient->create($x0,$y0,$x1,$y1);
    my $nc=0;
    my @cs;
    foreach(split(/,/,$d)){
	my $c=Gtk2::Gdk::Color->parse($_);
	unless($c){
	    &mess("Invalid color: $_","ERR");
	    undef $gsbg;
	    $cs[0]=Gtk2::Gdk::Color->new(0,0,0) if $nc==0; # the show must go on
	    $dare->modify_bg("normal",$cs[0]);
	    $sbg=$cs[0]->to_string;
	    $nc=0;
	    undef $gsbg; # no more try
	    last;
	}
	$cs[$nc]=$c;
	$nc++;
    }
    if($nc>1){
	for(my $k=0;$k<$nc;$k++){
	    $gsbg->add_color_stop_rgb($k/($nc-1),
				      $cs[$k]->red/65535,
				      $cs[$k]->green/65535,
				      $cs[$k]->blue/65535);
	}
    }else{
	# never reach here
	undef $gsbg;
    }
}

sub set_ps{
    my $pw=$current_stack==1?\$swin:\$logsw;
    my $z=$psidx>0?$psidx:1/(-$psidx);
    $hpage_skip=$$pw->get_hadjustment->page_size*$z;
    $vpage_skip=$$pw->get_vadjustment->page_size*$z;
}

sub with_snd{
    return if defined $sloop;
    eval{
	require "$mydir/imv_snd.pm";
	$sloop=&SND::init_snd($snddir);
    };
    if($@){
	&echo("#WARN:enabling sound effect failed: $@");
	$sloop=0;
    }
}

sub with_tnv{
#     2
#   |---|
# 1 |   | 3
#   |___|
#     4
#
#
    my $nw=defined($_[0])?$_[0]:0;
    if(defined($_[1])&&$_[1]==$tnvmode){
	&TNV::tnv_fnc(4,1); # grab_focus
	return;
    }
    return if(defined($splayer) && !$nw);
    return if($nw==1
	      &&$tnvmode==0&&defined$$ptnv); # reject new window when separated
    my $pnm=$_[1];
    my $nm;
    unless(@files){
	if($tnvmode>=0){
	    $nm=-1;
	}else{
	    return;
	}
    }elsif($nw==1){
	$nm=0;
    }elsif($nw==-1){
	$nm=-1;
    }
    if(!defined $ptnv || !defined $$ptnv){
	&echo("TNV loading");
	require "$mydir/imv_tnv.pm";
	$tnvf=$enc->decode($win->get_style->font_desc->get_family) unless $tnvf;
	&TNV::tnv_fnc(0,
		       [\@files,\@stats,\@thums,\$avr_aspect,
			\$nselect,\$dscale,\$kf,\$keyin,\$tnvmode,\$theA,\$theB,\$tnvf],
		       [\&show_image,
			\&config_cb,
			\&mv_entry,
			\&do_rm_entry,
			\&set_sbar,
			\&proc_key,
			\&get_fn,
			\&dopop,
			\&set_owh,
			\&set_ngpix,
			$sloop?\&SND::play_snd:undef,
			sub{$win->destroy;}]);
	&echo("TNV loaded");
    }
    ($tnvw,$tnvh)=(0,0);
    #-1:none,0:sep,1:vert,2:horiz
    my $noadd=0;
    given($tnvmode){
	when(-1){ # the first time
	    unless(defined $nm){
		$nm=1;
		$win->remove($vbox);
	    }else{
		#if $nw, $nm=0
		$noadd=1;
	    }
	}
	when(0){ # separated shown
	    $nm=1 unless defined $nm;
	    $win->remove($vbox);
	}
	when([1,2,3,4]){
	    $nm=$tnvmode==4?-1:$tnvmode+1 unless defined $nm;
	    $win->remove($thump);
	    $thump->remove($vbox);
	    $thump->remove($$ptnv);
	    undef $thump;
	}
    }

    &TNV::detach_tnv();
    $nm=$pnm if defined $pnm;
    $tnvmode=$nm;
    my $sc;
    given($nm){
	when(-1){
	    $win->add($vbox);
	    &config_cb(1);
	    &TNV::tnv_fnc(6,-1);
	    return;
	}
	when(0){
	    $ptnv=&TNV::mk_tnv(1,$tnvbgc,$tnvsbg,$pixdir);
	    &TNV::show_tnv($ptnv,$kf,$win->get_icon_list);
	    &TNV::set_ctid($kf);
	    $win->add($vbox) unless $noadd;
	    &config_cb(1);
	    return;
	}
	when([1,3]){
	    $thump=Gtk2::HPaned->new;
	    $sc=1;
	}
	when([2,4]){
	    $thump=Gtk2::VPaned->new;
	    $sc=2;
	}
    }
    $ptnv=&TNV::mk_tnv($sc,$tnvbgc,$tnvsbg,$pixdir);
    my ($w,$h)=&TNV::tnv_fnc(3);
    $w=24+(($nm % 2 == 1)?$w:$h+16); # <<<<<<<<
    if($tnvmode<3){# left,top
	$thump->add1($$ptnv);
	$thump->add2($vbox);
    }else{ # right, bottom
	$thump->add1($vbox);
	$thump->add2($$ptnv);
	if($tnvmode==3){
	    $w=($win->get_size)[0]-$w;
	}elsif($tnvmode==4){
	    $w=($win->get_size)[1]-$w;
	}
    }
    $win->add($thump);
    $thump->set_position($w);
    &show_all(1);
}

sub show_all{
    my $fv = $findb->visible;
    my $cv = $comb->visible;
    my $sv = $sbar->visible;
    $win->show_all;
    $comb->hide unless $cv;
    $findb->hide unless $fv;
    $sbar->hide unless $sv;
    &TNV::tnv_fnc(1,defined($kf)?$kf:0) if $_[0];  # show_tnv
}

sub show_osdmsg{
    $osdmsg=$_[0];
    $dare->queue_draw;
    Glib::Source->remove($osdmsgid) if defined $osdmsgid;
    $osdmsgid=Glib::Timeout->add_seconds(3,sub{
	undef $osdmsgid;
	undef $osdmsg;
	$dare->queue_draw;
	return FALSE });
}

sub toggle_splay{
# 0: from the begining
    if(defined $slider){
	Glib::Source->remove($slider);
	undef $slider;
    }
    my $posd=sub{
	    Glib::Source->remove($osder) if $osder;
	    $palps=-1;
	    $osdpix=$_[0];
	    $osder=Glib::Timeout->add(abs($tdelay)*2,sub #<<<<<<
			       {
				   $palps++;
				   $dare->queue_draw;
				   return TRUE;
			       });
    };
    if($sdelay>0){
	&SND::play_snd(2) if $sloop;
	$posd->($pausepix) if $pausepix;
	Glib::Source->remove(abs($splayer));
	$sdelay=-abs($sdelay);
    }else{
	&SND::play_snd(3) if($sloop && $Fin==0);
	$sdelay=abs($sdelay);
	$posd->($playpix) if($playpix && $Fin==0);
	if($splayer<0){
	    $splayer = -Glib::Timeout->add_seconds($sdelay,\&next_image,-2); # <<<
	}else{
	    $splayer =  Glib::Timeout->add_seconds($sdelay,\&next_image,2);
	}
    }
    $win->set_title(&mktitle) unless $_[0];
}

sub toggle_slide{
    # unable with tnv
    if(defined $splayer){
	Glib::Source->remove(abs($splayer)); undef $splayer;
	if(defined $slider){
	    Glib::Source->remove($slider);
	    undef $slider;
	}
	$dare->signal_handler_disconnect($excbid);
	$win->remove($dare);
	$win->add($vbox);
	undef $dare;
	undef $ppix;
	undef $gsbg if defined $gsbg;
	&config_cb(1); # <<<<
    }else{
	&with_tnv(1) if $tnvmode>0; # will pop tnv out
	$osdf=$enc->decode($win->get_style->font_desc->get_family) unless $osdf;
	$dare=Gtk2::DrawingArea->new;
	$excbid=$dare->signal_connect(expose_event=>\&slide_expose);
	$win->remove($vbox);
	$win->add($dare);
	# what if user changed the a or b during slide-show?
	# do nothing at present.
	$Fin=0;
	if(defined $theA && defined $theB && $theB<$theA){
	    $splayer = -Glib::Timeout->add_seconds($sdelay,\&next_image,-2); # <<<
	}else{
	    $splayer =  Glib::Timeout->add_seconds($sdelay,\&next_image,2);
	}
	undef $gsbg;
	if(defined $sbg){
	    my $c=Gtk2::Gdk::Color->parse($sbg);
	    unless($c){ # might be gradient
		$c=Gtk2::Gdk::Color->new(0,0,0);
		$gsbg=0;
	    }
	    $dare->modify_bg("normal",$c);
	}
	($cvsw,$cvsh)=$win->get_size;
	($darew,$dareh)=(0,0);
	unless(defined $endpix){
	    foreach("end","pause","play"){
		no strict "refs";
		eval{
		    ${$_."pix"}=Gtk2::Gdk::Pixbuf->new_from_file_at_scale("$pixdir/$_.png",
								      $maxw/4,$maxh/4,1);
		};
		${$_."pix"}=0 if $@; # no more try
	    }
	}
    }
    &show_image($splayer?$theA:undef);
    &show_all(); # <<<< ?
    $win->set_title(&mktitle);
}

sub switch_stack{
    return if defined $splayer;
    my $next_stack = defined $_[0]?$_[0]
	:$current_stack==$num_stack?1:$current_stack+1;
    $vbox->remove($comb);
    $vbox->remove($findb);
    $vbox->remove($sbar);
    given($current_stack){
	when(1){
	    $vbox->remove($swin);
	}
	when(2){
	    $vbox->remove($logsw);
	}
    }
    given ($next_stack){
	when(1){
	    $vbox->pack_start($swin, TRUE, TRUE, 0);
	}
	when(2){
	    $vbox->pack_start($logsw, TRUE, TRUE, 0);
	}
    }
    $vbox->pack_start($sbar, FALSE, FALSE, 0);
    $vbox->pack_start($findb, FALSE, FALSE, 0);
    $vbox->pack_start($comb, FALSE, FALSE, 0);
    &show_all();
    $current_stack=$next_stack;
    &set_ps;
}

sub player_run{
    return 0 if defined $player;
    $busy=0;
    $init_pos_u = $init_pos;
    $init_pos = 1;
    $player = Glib::Timeout->add($delay, \&play);
    return 1;
}
sub player_stop{
    return 0 unless defined $player;
    Glib::Source->remove($player);
    undef $player;
    $init_pos = $init_pos_u;
    return 1;
}
sub play{
    $busy++;
    return TRUE unless $busy == 1;
    $delay_count=0;
    &scan;
    $busy=$delay_count;
    return TRUE;
}

sub ws_event{
    my ($w,$e) = @_;
    foreach("maximized","fullscreen","above"){
	if ( $e->changed_mask & $_ ){
	    no strict "refs";
	    if ( $e->new_window_state & $_ ){
		$$_ = 1;
	    }else{
		$$_ = 0;
	    }
	}
    }
}

sub button_press_event{
    #my ($w,$e) = @_;
    # catch state at press, do action at release
    # state may be lost at release when stickey-key is on
#    ($ctrlm,$shftm,$metam)=($_[1]->get_state >= ['control-mask']?1:0,
#			    $_[1]->get_state >= ['shift-mask']?1:0,
#			    $_[1]->get_state >= ['mod1-mask']?1:0);
    $img->grab_focus;
    $isclick=1;
    $apos[0]=$swin->get_hadjustment->value+$_[1]->x_root if $swin->get_hscrollbar->visible;
    $apos[1]=$swin->get_vadjustment->value+$_[1]->y_root if $swin->get_vscrollbar->visible;
    &echo("button press:",$_[1]->button,$_[1]->type,$_[1]->state);
    return FALSE;
}

sub motion_notify_event{
#    my ($w,$e) = @_;
    if($isclick){
	$isdrag=1 unless $isdrag;
	$swin->get_hadjustment->set_value($apos[0]-$_[1]->x_root) if defined $apos[0];
	$swin->get_vadjustment->set_value($apos[1]-$_[1]->y_root) if defined $apos[1];
	return TRUE;
    }
}

sub button_release_event{
    #my ($w,$e) = @_;
    my $rc=TRUE;
    &echo("button release: ",$_[1]->button,$_[1]->type,$_[1]->state);
    unless($isdrag){
	if($ctrlm){
	}else{
	    given($_[1]->button){
		&next_image(1)    when(1);
		&dopop()          when(2);
		&next_image(-1)   when(3);
		default{$rc=FALSE;}
	    }
	    &set_sbar if $rc; #<<<<
	}
    }
    undef $isclick;
    undef $isdrag;
    undef @apos;
#    ($ctrlm,$shftm,$metam)=(0,0,0);
    return $rc;
}

sub dopop{
    return unless @files;
    require "$mydir/imv_pop.pm";
    &POP::init(\$dscale,\&repop) unless @pops;
    my $i=$#pops+1;
    for(0..$#pops){
	unless($pops[$_]){
	    $i=$_;
	    last;
	}
    }
    my $k=defined($_[0])?$_[0]:$kf;
    my $kff;
    unless($k==$kf){
	$kff=$kf;
	&show_image($k,1,1);
    }
    POP->new($i,$pix,&get_fn($k),$files[$k],\$pops[$i],
	     join("x",$stats[$kf]=~$owhqr));
    &show_image($kff,1,1) if defined $kff;
}

sub repop{ # for pops
    my ($pid,$show)=@_;
    my $kt;
    for(0..$#files){
	if($files[$_] eq $pops[$pid]->{efn}){
	    $kt=$_;
	    last;
	}
    }
    unless(defined($kt)){
	$pops[$pid]->destroy;
	return;
    }
    my $kff;
    unless($kt==$kf){
	$kff=$kf;
	&show_image($kt,$show?undef:1);
	&set_sbar;
    }
    return if $show;
    $pops[$pid]->{pix}=$pix;
    $pops[$pid]->setp;
    &show_image($kff,1) if defined $kff;
}

sub init_stats{
# 0: 1 -> $kf
#    0 -> $selected
#    2 -> all
    return unless @stats;
    my $initimg=0;
    my ($k1,$k2);
    my $sel=0;
    given($_[0]){
	when(0){
	    $sel=1;
	    $k1=0;
	    $k2=$#stats-1;
	}
	when(1){
	    return unless defined $kf;
	    $k1=$kf;
	    $k2=$kf;
	}
	when(2){
	    $k1=0;
	    $k2=$#stats-1;
	}
    }
    $keep_pos=0;
    $keep_flips=0;
    $keep_crop=0;
    $keep_scale=0;
    $keep_size=0;
    $keep_orient=0;
    $init_pos=0;
    &init_stat;
    for(my $k=$k1;$k<=$k2;$k++){
	next if $sel && substr($stats[$k],0,1)==0;
	my ($s,$g,$w,$h,$r)=split(/:/,$stats[$k],5);
	$stats[$k]=$s.":".$g.":".$w.":".$h.":";
	if(defined $kf && $k==$kf){
	    undef $kf;
	    &show_image($k); # must not call save_stat to keep need_init_pos=1
	}
    }
    &set_sbar;
}

sub init_stat{
    $need_init_pos = !$keep_pos;
    $rot      = 0 unless $keep_rot;
    $flipped  = 0 unless $keep_flips;
    $mirrored = 0 unless $keep_flips;
    @lurd     = (0)x4 unless $keep_crop;
    $scale    = 1 unless $keep_scale;
    $pwidth   = 0 unless $keep_size;
    $pheight  = 0 unless $keep_size;
    $updown   = 1 unless $keep_orient;
    $leftright= 0 unless $keep_orient;
}

sub load_stat{
    my $k = $_[0];
    my $pg=defined($_[1])?$_[1]:$gmode?\$gstats[$gmode]:undef;
    if(defined($pg)&&$$pg){
	$keep_rot    =substr($$pg,0,1);
	$keep_crop   =substr($$pg,1,1);
	$keep_flips  =substr($$pg,2,1);
	$keep_scale  =substr($$pg,3,1);
	$keep_size   =substr($$pg,4,1);
	$keep_pos    =substr($$pg,5,1);
	$keep_orient =substr($$pg,6,1);
	$init_pos    =substr($$pg,7,1);
	$animate     =substr($$pg,8,1);
	($theA,$theB)=split(/,/,substr($$pg,9));
	$theA=undef if(defined($theA) && length($theA)==0);
	$theB=undef if(defined($theB) && length($theB)==0);
    }
    my ($f,$m,$r,$ud,$rl)=(0)x5;
    my ($sel,$gl,$ow,$oh,$sf,$cr,$hv,$vv,$s,$w,$h)
	=split(/:/,$stats[$k]);
    &echo("loaded in: $k: ",$stats[$k]);
    unless(defined $h){
	&init_stat;
	return 0;
    }
    $f =substr($sf,0,1);
    $m =substr($sf,1,1);
    $r =substr($sf,2,1);
    $ud=substr($sf,3,1);
    $rl=substr($sf,4,1);

    $need_init_pos = 0;
    $ha_value = $hv unless $keep_pos;
    $va_value = $vv unless $keep_pos;
    $flipped  = $f  unless $keep_flips;
    $mirrored = $m  unless $keep_flips;
    $rot      = $r  unless $keep_rot;
    $updown   = $ud unless $keep_orient;
    $leftright= $rl unless $keep_orient;
    @lurd     = ($cr?split(/,/,$cr):(0)x4) unless $keep_crop;
    $scale    = $s  unless $keep_scale;
    $pwidth   = $w  unless $keep_size;
    $pheight  = $h  unless $keep_size;
    return 1;
}

sub save_stat{
    return unless $pix;
    my $k = $_[0];
    my $pg=defined($_[1])?$_[1]:$gmode?\$gstats[$gmode]:undef;
    if($pg){
	$$pg=$keep_rot
	    .$keep_crop
	    .$keep_flips
	    .$keep_scale
	    .$keep_size
	    .$keep_pos
	    .$keep_orient
	    .$init_pos
	    .$animate
	    .(defined($theA)?$theA:"").",".(defined($theB)?$theB:"");
    }
    $pwidth   = $pix->get_width;
    $pheight  = $pix->get_height;
    $ha_value = $swin->get_hadjustment->value;
    $va_value = $swin->get_vadjustment->value;
    &echo("save_stat: in: $stats[$k]");
    my $sel=substr($stats[$k],0,1);
    my ($gid)=$stats[$k]=~$gidqr;
    $gid=0 unless defined $gid;
    my ($ow,$oh)=$stats[$k]=~$owhqr;
    $ow=0 unless defined $ow;
    $oh=0 unless defined $oh;
    my $sf=$flipped.$mirrored.$rot.($updown?1:0).($leftright?1:0);

    $stats[$k]=
	join(":",($sel,
		  $gid,
		  $ow,$oh,
		  $sf,
		  (($lurd[0]||$lurd[1]||$lurd[2]||$lurd[3])?join(",",@lurd):0),
		  $ha_value,
		  $va_value,
		  (sprintf "%.4f", $scale),
		  $pwidth,
		  $pheight));
    &echo("saved: $k: ",$stats[$k]);
}

sub get_orig{
    # wrt adjustment
    my ($lr,$ud);
    if($_[0]){ # edge
	($lr,$ud)=(!$leftright,!$updown);
    }else{ # orig
	($lr,$ud)=($leftright,$updown);
    }
    return ($lr?0:$swin->get_hadjustment->upper
	    -$swin->get_hadjustment->page_size,
	    $ud?0:$swin->get_vadjustment->upper
	    -$swin->get_vadjustment->page_size);
}

sub scan{
    my $d=$shft;
    $d=!$d if(defined($player) && defined($theA) && defined($theB) && ($theA>$theB));
    &scroll(($d?"-":"+")."p", $page_scan<=0?"v":$page_scan==1?"h":"v");
}

sub scroll{
    my ($d, $f) = @_;
    # $d: value, s, c, {+|-}P, {+|-}p
    my $fnc="get_".$f."adjustment";
    my $sbon;
    if($page_scan>0&&$current_stack==1){
	my $invfnc="get_".$invori{$f}."adjustment";
	$sbon=$swin->$invfnc->upper-$swin->$invfnc->page_size;
    }
    my $adj=$current_stack==1?$swin->$fnc:$logsw->$fnc;
    my $ps=$adj->page_size; # the default
    my $up=$adj->upper-$ps;
    my $v=$adj->value;
    $ps=$f eq "v"?$vpage_skip:$hpage_skip if($d=~/^.+p/);
    my ($endv,$startv);
    my $dir = ($f eq "v")?$updown:$leftright;
    if($dir){
	($endv,$startv)=($up,0);
    }else{
	$ps *= -1; # if dir + is increasing
	($endv,$startv)=(0,$up);
    }
    # +/-p* moves to +/-$dir direction
    if($d=~/^\+p/i){
	if($v==$endv && $current_stack==1){
	    if($page_scan==$pshash{$f}){
		&scroll($dir?-10000:10000, $f) if $sbon;
		&scroll($d, $invori{$f});
	    }else{
		&next_image(1);
	    }
	    return;
	}else{
	    $v += $ps;
	    $v=$endv if($psidx<0&&$endv-$v<=$screps);
	}
    }elsif($d=~/^-p/i){
	if($v==$startv && $current_stack== 1){
	    if($page_scan==$pshash{$f}){
		&scroll($dir?10000:-10000, $f) if $sbon;
		&scroll($d, $invori{$f});
	    }else{
		&next_image(-1);
	    }
	    return;
	}else{
	    $v -= $ps;
	}
    }elsif ($d eq "c"){
	$v = $up/2;
    }elsif ($d eq "s"){
	$v = $_[2];
	$v=$up if($v<0);
    }else{
	$v += $d;
    }

    if ( $v < 0 ){
	$v = 0;
    }elsif( $v > $up ){
	$v = $up;
    }
    $adj->set_value($v);
}

sub delete_event{
    $synclog=0; # $synclog=1 causes errors at &mess
    &end_ipc if defined $ipctag;
    &end_rcout(1) if defined $rctag;
    &end_files(-1) if defined $frtag;
    &player_stop if $player;
    &SND::end_snd if $sloop;
    &clean_pids();
    system("$rmdir_com ".&a2arg(@tmpdirs)) if @tmpdirs;
    Gtk2->main_quit if defined $win;
    print "\n".(length(ref $_[0])?"thank you\n٩(๑❛ᴗ❛๑)۶":"bye\n(;^_^)")."\n";
    exit unless defined $win; # exit may cause segfau
    return FALSE;
}

sub mv_entry{
    #my ($incre,$ctid,$abs)=@_
    return unless @files;
    my $ck = defined($_[1])?$_[1]:$kf;
    my $tk = defined($_[2])?$_[0]:$ck + $_[0];
    $tk=$tk<0?$#files:($tk>$#files?0:$tk);
    ($files[$ck], $files[$tk]) = ($files[$tk], $files[$ck]);
    ($stats[$ck], $stats[$tk]) = ($stats[$tk], $stats[$ck]);
    ($thums[$ck], $thums[$tk]) = ($thums[$tk], $thums[$ck]) if @thums;
    $kf = $tk unless defined $_[1];
    return $tk;
}

sub rm_all{
    if(@baks){
	foreach(@baks){
	    my ($pfs,$pss,$pns)=($_->[1],$_->[2],$_->[5]);
	    for(my $k=0;$k<=$#stats;$k++){
		for(my $j=0;$j<=$#$pfs;$j++){
		    if($$pfs[$j] eq $files[$k]){
			$$pns-- if substr($$pss[$j],0,1);
			$$pss[$j]="D";
			last;
		    }
		}
	    }
	    for(my $k=$#$pss;$k>=0;$k--){
		if($$pss[$k] eq "D"){
		    splice(@$pfs,$k,1);
		    splice(@$pss,$k,1);
		}
	    }
	}
    }
    undef @files;
    &no_image;
}

sub do_rm_entry{
    return unless @files;
    my $k=$_[0];
    $k=$kf unless defined $k;
    return if($k>$#files||$k<0);
    if(substr($stats[$k],0,1)){
	$nselect--;
    }
    if(@baks){
	foreach(@baks){
	    my ($pfs,$pss,$pns)=($_->[1],$_->[2],$_->[5]);
	    for(my $j=0;$j<=$#$pfs;$j++){
		if($$pfs[$j] eq $files[$k]){
		    $$pns-- if substr($$pss[$j],0,1);
		    splice(@$pfs,$j,1);
		    splice(@$pss,$j,1);
		    last;
		}
	    }
	}
    }

    splice(@files,$k,1);
    splice(@stats,$k,1);
    splice(@thums,$k,1) if defined $thums[$k];

    if(!@files){
	&no_image;
    }elsif($kf>$#files){
	$kf=$#files;
    }
    &load_stat($kf) if defined($kf)&&$kf==$k;
}

sub restore_stat{
    my ($p,$skip_scale)=@_;
    return $p if $set_ani; # <<<< why he comes here
    &echo("rot=$rot, flipped=$flipped, mirrored=$mirrored");
    if($lurd[0]||$lurd[1]||$lurd[2]||$lurd[3]){
	&echo("crop=$lurd[0],$lurd[1],$lurd[2],$lurd[3]");
	my ($w,$h)=($p->get_width,$p->get_height);
	$p=$p->new_subpixbuf($lurd[0],$lurd[1],
			     $w-($lurd[0]+$lurd[2]),
			     $h-($lurd[1]+$lurd[3]));
    }
    $p = $p->rotate_simple($rot_type[$rot]) if $rot;
    $p = $p->flip(0) if $flipped;
    $p = $p->flip(1) if $mirrored;
    return $p if defined $skip_scale ;
    my ($ww,$hh);
    if(!$keep_size&&$scale>0){
	return $p if $scale==1;
	($ww,$hh)=($p->get_width*$scale,$p->get_height*$scale);
    }else{
	return $p if ( $pwidth == $p->get_width && $pheight == $p->get_height );
	($ww,$hh)=($pwidth,$pheight);
    }
    return $p->scale_simple($ww,$hh,"hyper");
}

sub change_view_size{
    $swin->set_policy("automatic","automatic");
    &echo("change_view_size: set_size_request: $_[0], $_[1]");
    $img->set_size_request($_[0],$_[1]);
    $swin->resize_children;
    $img->set_size_request(-1,-1);
    &set_view_size($_[0],$_[1]);
}

sub set_view_size{
    my ($w,$h)=@_;
    my ($ww,$hh) =
	($w+($swin->style->xthickness)*2,
	 $h+($swin->style->ythickness)*2);
    $hh += $sbar->allocation->height if $sbar->visible;
    $hh += $findb->allocation->height if $findb->visible;
    $hh += $comb->allocation->height if $comb->visible;
    if($tnvmode>0){
	if($tnvmode % 2 == 1){
	    $ww+=$tnvw+$tphs;
	}elsif($tnvmode % 2 == 0){
	    $hh+=$tnvh+$tphs;
	}
    }
    my ($rcw,$rch);
    if($ww>$maxw){
	$ww=$maxw;
	$rcw=1;
    }
    if($hh>$maxh){
	$hh=$maxh;
	$rch=1;
    }
    &echo("set_view_size: resize: $ww, $hh");
    $win->resize($ww, $hh);
    return ($rcw,$rch);
}

sub set_ngpix{
    return $ngpix if defined $ngpix;
    eval{
	$ngpix=Gtk2::Gdk::Pixbuf->new_from_file_at_scale("$pixdir/hatena.png",
							 $maxw/2,$maxh/2,1);
    };
    $ngpix=0 if $@; # no more try
    return $ngpix;
}

sub show_image{
    #0: index to show: $kf if undef
    #1: dont call TNV::set_ctid: false
    #2: dont call set_pix
    return unless @files;
    my $k = $_[0];
    &echo("show_image:",$k?$k:"undef");
    $k=$kf if(! defined $k && defined $kf);
    return if(! defined $k || $k < 0 || $k > $#files );

    if((defined $player||defined $splayer)
       && (defined $theA||defined $theB)){
	my $a=defined $theA?$theA:0;
	my $b=defined $theB?$theB:$#files;
	if($b>$a){ # a -> b
	    $k=$a if $k<$a;
	    if($k>$b){
		$k=$a;
		$Fin=1 if($Fin==0&&$a!=$b);
	    }
	}else{ # b <- a
	    if($k<$b){
		$k=$a;
		$Fin=1 if($Fin==0&&$a!=$b);
	    }
	    $k=$a if $k>$a;
	}
	$Fin=0 if $a==$b;
    }

    if(defined $splayer){
	&SND::play_snd($Fin?4:1) if $sloop;
	if($Fin==1){
	    $pix=$endpix?$endpix:undef;
	    $kalps=0;
	    Glib::Source->remove(abs($splayer));
	    $sdelay=-abs($sdelay);
	    $slider=Glib::Timeout->add(abs($tdelay),sub
				   {
				       $kalps++;
				       $dare->queue_draw;
				       return TRUE;
				   });
	    $win->set_title("$me =END=");
	    return;
	}elsif($Fin==2){
	    $Fin=0;
	}
    }elsif(defined $player){
	&SND::play_snd(0) if $sloop;
    }

    &echo("show_image: pass: $k,",$kf?$kf:"undef");
    &save_stat($kf) if defined $kf; # because no "stat changed detection"
    if ( defined $_[0] || (defined $kf && $k != $kf) || $animate != $set_ani ){
	my $f=&get_fn($k);
	my $i=(Gtk2::Gdk::Pixbuf->get_file_info($f))[0];
	unless(defined $i){
	    &mess("failed to load file: $f\n","ERR");
	    &smess($tmpmsg="Load Error: ".$f);
	    $pix=&set_ngpix;
	    return unless $pix;
	}else{
	    if ( !$animate || ! &can_animate(&get_fn($k))){
		$set_ani = 0;
		$pix = Gtk2::Gdk::Pixbuf->new_from_file($f);
	    }else{
		$set_ani = 1;
		$pix = Gtk2::Gdk::PixbufAnimation->new_from_file($f);
	    }
	}
	$pixbak=$pix;
	$kf=$k;
	$win->set_title(&mktitle);
    }
    &load_stat($k);
    &TNV::set_ctid($kf) if(!$_[1]&&$tnvmode>=0); #<<<<<<<<<<<<<<<<<<<<<<<<,,
    &do_trans($_[2]);
    unless(defined $splayer || $_[2]){
	($ha_value,$va_value)=&get_orig(undef) if(defined $_[0]
						  &&($init_pos||$need_init_pos));
	$swin->get_hadjustment->set_value($ha_value);
	$swin->get_vadjustment->set_value($va_value);
	if(defined($player)){
	    &set_sbar;
	    $delay_count=$delay_on_np if $swin->get_vscrollbar->visible;
	}
    }
}

sub next_image{
    #0: next if >0, otherwise prev. is slider call back if abs>1
    return if(!@files || !defined($kf));
    return TRUE if(abs($_[0])>1 && $slider); # <<<< busy
    $Fin=1 if((($kf==$#files && $_[0]>1)
	      ||($kf==0 && $_[0]<-1))
	      && $Fin==0);
    &show_image($_[0]>0?($kf<$#files?$kf+1:0):($kf>0?$kf-1:$#files));
    return TRUE;
}

sub no_image{
    undef @files;
    undef @stats;
    undef @thums;
    undef $kf;
    $nselect=0;
    $avr_aspect=0;
    unless(@baks){
	undef @dirs;
	undef @gids;
	undef @arxvs;
	undef %opath;
	system("$rmdir_com ".&a2arg(@tmpdirs)) if @tmpdirs;
    }
    if($gmode){
	for(my $j=0;$j<$#gids;$j++){
	    if($gmode==$gids[$j]){
		splice @gids, $j, 1;
		last;
	    }
	}
    }
    $img->clear;
    undef($pix);
    undef($pixbak);
    $win->resize($defw,$defh);
    $win->set_title(&mktitle);
    my $ico=($win->get_icon_list)[4];
    $img->set_from_pixbuf($ico) if $ico;
    $cmode=0;
    undef $theA;
    undef $theB;
    &set_sbar;
}

sub set_pix{
    # my $fitW=shift;
    return unless defined $pix;
    my ($w,$h)=($pix->get_width,$pix->get_height);
    ($pwidth,$pheight)=($w,$h);
    unless($splayer){
	unless($set_ani){
	    my ($aw,$ah);
	    given($fit){
		($aw,$ah)=("never","never") when("f");
		($aw,$ah)=("never","automatic") when("fw");
		($aw,$ah)=("automatic","never") when("fh");
		default{($aw,$ah)=("automatic","automatic")};
	    }
	    $swin->set_policy($aw,$ah);
	    $img->set_from_pixbuf($pix);
	}else{
	    $swin->set_policy("automatic","automatic");
	    $img->set_from_animation($pix);
	}
	if($fit =~ /^W/||$_[0]){
	    &echo("set_pix: fit=W $w,$h");
	    my ($aw,$ah)=&set_view_size($w,$h);
	    $swin->set_policy($aw?"automatic":"never",
			      $ah?"automatic":"never");
	}
	if(defined($ebox)){
	    # $ebox prevents $swin->adjustments set correctly without this
	    Gtk2->main_iteration while Gtk2->events_pending;
	}
	$swin->resize_children;
	$swin->set_policy("automatic","automatic");
    }else{
	if($tdelay>0&&$sdelay>0){
	    unless($slider){
		$kalps=0;
		$slider=Glib::Timeout->add($tdelay,sub
					   {
					       $kalps++;
					       $dare->queue_draw;
					       return TRUE;
					   });
	    }
	}else{
	    $dare->queue_draw;
	}
    }
    &set_ps;
}

sub slide_expose{
    require "Cairo.pm";
    my $cr=Gtk2::Gdk::Cairo::Context->create($dare->window);
    my ($ww,$wh)=$dare->window->get_size;
    my ($a,$b,$j);
    &set_gsbg($darew=$ww,$dareh=$wh) if($darew!=$ww&&$dareh!=$wh&&defined($gsbg));
    if(defined $gsbg){
	$cr->set_source($gsbg);
	$cr->fill;
	$cr->paint;
    }
    if(defined $ppix && defined $slider){
	if($kalps<=$#alps){
	    $b=$alps[$kalps];
	    $a=1-$b;
	}elsif(($j=2*$#alps-$kalps)>=0){
	    $a=$alps[$j];
	    $b=1-$a;
	}else{
	    # the end of transition
	    $b=1;
	    $a=0;
	    Glib::Source->remove($slider); undef $slider;
	    if($Fin==1){ # loop slideshow
		$Fin=2;
		&toggle_splay(1);
	    }
	    return unless defined $pix;
	    $ppix=$pix;
	}
	Gtk2::Gdk::Cairo::Context::set_source_pixbuf($cr,$ppix,
						     ($ww-$ppix->get_width)/2,
						     ($wh-$ppix->get_height)/2);
	$cr->paint_with_alpha($a);
    }else{ # the first time
	if(defined $slider){
	    Glib::Source->remove($slider); undef $slider;
	}
	$b=1;
	$ppix=$pix;
    }
    return unless defined $pix;
    Gtk2::Gdk::Cairo::Context::set_source_pixbuf($cr,$pix,
						 ($ww-$pix->get_width)/2,
						 ($wh-$pix->get_height)/2);
    $cr->paint_with_alpha($b);

    if(defined $palps){
	if($palps<=$#alps){
	    my $a=$alps[$#alps-$palps]*2;
	    Gtk2::Gdk::Cairo::Context::set_source_pixbuf($cr,$osdpix,
							 ($ww-$osdpix->get_width)/2,
							 ($wh-$osdpix->get_height)/2);
	    $cr->paint_with_alpha($a);
	}else{
	    Glib::Source->remove($osder);
	    undef $palps;
	    undef $osder;
	}
    }

    my $posd=sub{
	# 0: mess
	# 1: $cvsw/size
	# 2: $fs*margin_size
	# 3: x origin 0 to 1
	# 4: y origin 0 to 1
	my $fs=$cvsw/$_[1];
	$cr->select_font_face($osdf,"normal","normal");
	$cr->set_font_size($fs);
	my $m=int($fs*$_[2]);
	$m++ if $m&1;
	my ($tw,$th,$bx,$by)=($cr->text_extents($_[0])->{"width"},
			      $cr->text_extents($_[0])->{"height"},
			      $cr->text_extents($_[0])->{"x_bearing"},
			      $cr->text_extents($_[0])->{"y_bearing"});
	my ($rw,$rh)=($tw+$m,$th+$m);
	#
	my ($xo,$yo)=($_[3]*($cvsw-$rw),$_[4]*($cvsh-$rh));
	$cr->set_source_rgba(0,0,0,0.5);
	my $r=$m; #<<<
	my $deg=.01745329251994329576923690768488;
	$cr->new_sub_path();
	$cr->arc($xo+$rw-$r, $yo+$r,     $r, -90*$deg,   0);
	$cr->arc($xo+$rw-$r, $yo+$rh-$r, $r,   0,       90*$deg);
	$cr->arc($xo+$r,     $yo+$rh-$r, $r,  90*$deg, 180*$deg);
	$cr->arc($xo+$r,     $yo+$r,     $r, 180*$deg, 270*$deg);
	$cr->close_path();
	$cr->fill;
	#
	$cr->move_to($xo-$bx+$m/2,$yo-$by+$m/2); #<<<
	$cr->set_source_rgb(1,1,1);
	$cr->show_text($_[0]);
    };

    if($sdelay<0&&!$osder&&$Fin==0){
	$posd->("paused",16,0.8,0.5,0.5);
    }

    if(defined $osdmsg){
	$posd->($osdmsg,16,0.4,0,0);
    }

    if($osdtype>0){
	(my $f=$files[$kf])=~s/$didqr//;
	$f=$dirs[$1]."/$f" if $osdtype==2;
	$posd->($f,32,0.4,1,1);
    }
}

sub resize{
# my $dscale,$x,$y=@_
    return unless defined $pixbak;
    if($set_ani){
    	$pix=$pixbak;
	return;
    }
    my $p=&restore_stat($pixbak, TRUE);
    my ($w,$h)=($p->get_width,$p->get_height);
    my ($ww,$hh);
    if(defined $_[0]){
	if((my $ds=$_[0])=~/^f/){
	    my ($ww0,$hh0)=($cvsw,&get_vh);
	    ($ww,$hh)=($ww0,$hh0);
	    if($ds eq "fw"){
		$ww-=$slw
		    +$swin->get_vscrollbar->style->xthickness*2 if $h*$ww0>$hh0*$w;
		$hh=$h*$ww/$w;
	    }elsif($ds eq "fh"){
		my $m=$slw+$swin->get_hscrollbar->style->ythickness*2;
		if($w*($hh0-$m)>$ww0*$h){
		    $hh-=$m;
		}elsif($w*$hh0>$ww0*$h){
		    $hh=$ww0*$h/$w;
		}
		$ww=$w*$hh/$h;
	    }else{
		if($ww0/$w<$hh0/$h){
		    $hh=$h*$ww0/$w;
		}else{
		    $ww=$w*$hh0/$h;
		}
	    }
	    $scale=$ww/$w;
	}elsif($_[0] eq "s"){
	    ($ww,$hh)=($w*$scale,$h*$scale);
	}elsif($_[0]){
	    $scale=-$scale if $scale<0; # <<<<
	    my $s=$scale+$_[0];
	    $s=$scale if $s<0;
	    ($ww,$hh)=($w*$s,$h*$s);
	    $scale=$s;
	}else{
	    ($ww,$hh)=($w,$h);
	    $scale=1;
	    $pix=$p;
	    return;
	}
    }else{ # (x,y) must be passed
	($ww,$hh)=(defined($_[1])?$_[1]:$w,defined($_[2])?$_[2]:$h);
	$scale=-abs($scale); # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    if($ww>0&&$hh>0){
	# nearest, tiles, bilinear, hyper
	$pix=$p->scale_simple($ww,$hh,"hyper");
    }else{
	$tmpmsg="Invalid size for scale";
	$pix=$p;
    }
}

sub do_trans{
    # 0: dont call set_pix
    return unless $pix;
    if($fit=~/^f/){
	&resize($fit);
    }else{
	$scale=1 if($fit=~/=/); # overwrites keep-scale mode
	$pix=&restore_stat($pixbak);
    }
    &set_pix() unless $_[0];
}

sub smess{
    return unless defined $_[0];
    &echo("smess: $_[0]");
    $sbar->pop($context_id);
    $sbar->push($context_id, $_[0]);
}

sub mess{
    &echo("#$_[1]:$_[0]") if(defined $_[0] && defined $_[1]
			     && ($_[1] eq "WARN"||$_[1] eq "ERR"));
    return unless $log;
    if(defined $_[0]){
	$log->insert_with_tags_by_name($log->get_end_iter,
				       $_[0],
				       defined($_[1])?$_[1]:"MSG",
				       defined($_[2])?$_[2]:"NORM");
	if($synclog){
	    $win->window->process_all_updates;
	    #Gtk2->main_iteration while Gtk2->events_pending;
	    #Gtk2->main_iteration_do(0);
	}
    }
    my $a=$logsw->get_vadjustment;
    return if(!defined $_[0]
	      &&($a&&$a->get_value!=$a->upper-$a->page_size));
    $log->place_cursor($log->get_end_iter);
    $logw->scroll_to_mark($log->get_insert, 0,0,0,0);  #<<<<<<<<<<<<<<<<<<<
    &set_ps if $current_stack==2;
}

sub show_main{
    #my ($dec_on,$bgc,$font)=@_;

    # dummy window to get slider-width
    {
	my $dw=Gtk2::Window->new('popup');
	my $s=Gtk2::VScrollbar->new();
	$dw->add($s);
	$dw->show_all;
	$slw=$s->allocation->width; #idontknowhowtoaccessslider-widthofGtk2::Range
	$dw->hide;
	$dw->remove($s);
	undef $s;
	undef $dw;
    }

    ($maxw,$maxh)=Gtk2::Gdk::get_default_root_window(0)->get_size;
    $win   = Gtk2::Window->new('toplevel');
##    $win = Gtk2::Gdk::Window->new(undef, { window_type => 'toplevel',
##					   wclass => 'GDK_INPUT_OUTPUT',
##					   width => 200,
##					   height => 100 });
    $win->set_default_size($defw, $defh);
    $win->set_border_width(0); # <<<
    $win->set_decorated($_[0]);
    $win->set_title($me);
    $vbox  = Gtk2::VBox->new(FALSE, 0);
    $swin  = Gtk2::ScrolledWindow->new();
    $img   = Gtk2::Image->new();
    $sbar  = Gtk2::Statusbar->new();
    $findb = Gtk2::HBox->new(FALSE,0);
    $findw = Gtk2::Entry->new();
    $findl = Gtk2::Label->new();
    $comb  = Gtk2::HBox->new(FALSE,0);
    $comw  = Gtk2::Entry->new();
    $coml  = Gtk2::Label->new();
    $context_id = $sbar->get_context_id("main");
    #
    $logsw = Gtk2::ScrolledWindow->new;
    $logw  = Gtk2::TextView->new();
    $logw->modify_font(
	Gtk2::Pango::FontDescription->from_string(
	    length($_[2])>1?$_[2]:"Monospace 11")) if defined $_[2];
    $logw->set_editable( FALSE );
    $log   = $logw->get_buffer;
    $log->create_tag("MSG",  "foreground", "black",);
    $log->create_tag("ERR",  "foreground", "red",  "weight", 700);
    $log->create_tag("WARN", "foreground", "red");
    $log->create_tag("OK",   "foreground", "blue", "weight", 700);
    $log->create_tag("SEL",  "foreground", "blue");
    $log->create_tag("CUR",  "foreground", "seagreen");
    $log->create_tag("BOLD", "weight", 700 );
#    $log->create_tag("SLNT", style => "italic");
    $log->create_tag("NORM", "weight", 300 );
    $logsw->set_policy("automatic","automatic");
    $logsw->add($logw);

    #------------------------------------------------------------------
    $swin->can_focus(FALSE);
    $img->can_focus(TRUE);
    $logsw->can_focus(FALSE);
    $win->signal_connect(delete_event => sub{$win->destroy;});
    $win->signal_connect(destroy => \&delete_event);
    $pproc_key_x=\&proc_key_1;
    $win->signal_connect(key_press_event => \&key_press_event);
    $win->signal_connect(key_release_event => sub{($shft,$ctrl,$meta)=(0,0,0);});
    $win->signal_connect(window_state_event => \&ws_event);
    $win->signal_connect(configure_event => \&config_cb);
    if(defined $_[1]){
	$ebox=Gtk2::EventBox->new();
	$ebox->signal_connect(button_press_event => \&button_press_event);
	$ebox->signal_connect(button_release_event => \&button_release_event);
	$ebox->signal_connect(motion_notify_event => \&motion_notify_event);
	$ebox->set_events([qw/leave-notify-mask
                            button-press-mask
                            button-release-mask
                            pointer-motion-mask/]);
#                            pointer-motion-hint-mask/]);
	$ebox->add($img);
	my $bgc=Gtk2::Gdk::Color->parse($_[1]);
	$ebox->modify_bg("normal",$bgc) if defined $bgc;
	$swin->add_with_viewport($ebox);
    }else{
	$swin->signal_connect(button_press_event => \&button_press_event);
	$swin->signal_connect(button_release_event => \&button_release_event); # wont work
	$swin->add_with_viewport($img);
    }

    #------------------------------------------------------------------
    $swin->set_policy("automatic","automatic");
    $vbox->pack_start($swin,TRUE,TRUE,0);
    $vbox->pack_start($sbar,FALSE,FALSE,0);
    $findb->pack_start($findw,TRUE,TRUE,0);
    $findb->pack_start($findl,FALSE,FALSE,0);
    $comb->pack_start($comw,TRUE,TRUE,0);
    $comb->pack_start($coml,FALSE,FALSE,0);
    $vbox->pack_start($findb,FALSE,FALSE,0);
    $vbox->pack_start($comb,FALSE,FALSE,0);
    $win->add($vbox);

    eval{
	$win->set_icon_list((Gtk2::Gdk::Pixbuf->new_from_file("$pixdir/imv16.png"),
			     Gtk2::Gdk::Pixbuf->new_from_file("$pixdir/imv32.png"),
			     Gtk2::Gdk::Pixbuf->new_from_file("$pixdir/imv48.png"),
			     Gtk2::Gdk::Pixbuf->new_from_file("$pixdir/imv64.png"),
			     Gtk2::Gdk::Pixbuf->new_from_file("$pixdir/imv128.png")));
	my $ico=Gtk2::Gdk::Pixbuf->new_from_file("$pixdir/imv128wcr.png");
	$img->set_from_pixbuf($ico);
    };
    if($@){
	chomp($@);
	&echo("#WARN:$@");
    }

    $win->show_all;
    $findb->hide;
    $comb->hide;
    my $wm="$me version $VERSION ready";
    &mess("$wm\n");
    &smess("$wm");
}

sub set_sbar{
    if(defined($tmpmsg)&&$tmpmsg){
	&smess($tmpmsg) if $sbar->visible;;
	$tmpmsg=0;
	return 2;
    }elsif(!$tmpmsg){
	undef $tmpmsg;
    }
    return 0 unless $sbar->visible;
    &smess($psbarstr->());
    return 1;
}

sub mktitle
{return $me
     .(defined($kf)?
     (defined($splayer)?($sdelay>0?" =PLAY=":" =PAUSED=")
     :"")
     .(" [".&get_fn($kf)."]")
     :"");
}

sub sort_f{
    #0: type
    #1: return the sorted key array. internal call.
    return unless @files;
    my $key=$_[0];
    my $dir=($key=~/-/?-1:1);
    my @dc;
    my $i=0;
    given ($key){
	$i = 9 when(/t/);
	$i = 7 when(/s/);
	when(/z/){
	    for(my $k=0;$k<=$#files;$k++){
		$dc[$k]=&get_fn($k);
	    }
	    $i=-1;
	}
	when(/n/){
	    for(my $k=0;$k<=$#files;$k++){
		my $f=$files[$k];
		$f=~s/$didqr//;
		$dc[$k]=$f;
	    }
	    $i =-2;
	}
	when(/x/){
	    for(my $k=0;$k<=$#files;$k++){
		my ($q,$w,$h)=Gtk2::Gdk::Pixbuf->get_file_info(&get_fn($k));
		$dc[$k]=$w*$h;
	    }
	    $i =-3;
	}
	when(/g/){
	    for(my $k=0;$k<=$#files;$k++){
		if($stats[$k]=~$gidqr){
		    $dc[$k]=$1;
		}else{
		    $dc[$k]=0;
		}
	    }
	    $i =-4;
	}
    }

    return if($i==0&&$#files==0);

    my $fn=$files[$kf] if defined $kf;
    if($i>0){
	for(my $k=0;$k<=$#files;$k++){
	    $dc[$k]=(stat(&get_fn($k)))[$i];
	}
    }
    &save_stat($kf);
    undef @thums if @thums;
    if($i==0){
	# randomize
	for(my $k=0;$k<=$#files;$k++){ #$k+=2){
	    my $j=int(rand($#files)+0.5);
	    ($files[$k],$files[$j])=($files[$j],$files[$k]);
	    ($stats[$k],$stats[$j])=($stats[$j],$stats[$k]);
	}
    }else{
	my @fss;
	for(my $k=0;$k<=$#files;$k++){
	    $fss[$k][0]=$dc[$k];
	    $fss[$k][1]=$files[$k];
	    $fss[$k][2]=$stats[$k];
	}
	undef @dc;
	my ($A,$B)=$dir>=0?("a","b"):("b","a");
	given($i){
	    when([9,7,-3]){
		no strict "refs";
		@fss = sort { @$$A[0] <=> @$$B[0] } @fss;
	    }
	    default{
		no strict "refs";
		@fss = sort { @$$A[0] cmp @$$B[0] } @fss;
	    }
	}
	for(my $k=0;$k<=$#files;$k++){
	    $files[$k]=$fss[$k][1];
	    $stats[$k]=$fss[$k][2];
	}
	if($_[1]){
	    for(my $k=0;$k<=$#files;$k++){
		$dc[$k]=$fss[$k][0];
	    }
	}
	undef @fss;
    }

    return @dc if $_[1];

    if(defined($fn)){
	for(my $k=0;$k<=$#files;$k++){
	    if($files[$k] eq $fn){
		$kf=$k;
		&show_image($kf);
		&TNV::set_ctid($kf,1) if $tnvmode>=0;
		last;
	    }
	}
    }
    $tmpmsg="Sorted ".$key;
    &mess("$tmpmsg\n");
}

sub end_files{
    kill 9, @frpids if($_[0]!=0&&@frpids);
    close($frh);
    Gtk2::Helper->remove_watch($frtag);
    undef $frtag; undef $frh; undef @frpids;
    $win->window->set_cursor(undef) if($win && $win->window);
    return if $_[0]==-1;
    if(@files){
	if(defined($nfsorter)){
	    &sort_f($nfsorter);
	    undef $nfsorter if substr($nfsorter,0,1) ne "+";
	}
	&start_keysim($stacom,1) if defined $stacom;
	&show_image(0) unless defined $kf;
    }
    if(defined($echo_tty)){ #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
	&switch_stdout();
	undef $echo_tty;
    }
    # archives in an archive should be relative
    for(my $k=0;$k<=$#arxvs;$k++){
	my $a=$arxvs[$k];
	foreach my $p (keys(%opath)){
	    if($a=~/^$p/){
		my $ka=$opath{$p};
		$a=~s/^$p(.+)/$ka:$1/;
		$arxvs[$k]=$a;
		last;
	    }
	}
    }
    &mess($tmpmsg="Reading files canceled.") if $_[0]==1;
    &set_sbar;
}

sub get_lines{
    my ($fh) = @_;
    my $line;
    my $buf;
    my $bsz=128;
    my @ls;
    while(1){
	my $n=sysread($fh,$buf,$bsz);
	unless($n){
	    unless($buf){
		last if defined $line;
		return @ls;
	    }
	    last;
	}
	$line.=$buf;
    }
    @ls=split(/\n/,$line);
    return @ls;
}

sub clean_pids{
    return unless @pids;
    if(defined($_[0])){
	for(my $k=0;$k<=$#pids;$k++){
	    if($_[0]==$pids[$k]){
		splice(@pids,$k,1);
		last;
	    }
	}
    }else{
	foreach(@pids){
	    kill 'TERM', $_ if kill(0,$_);
	}
    }
}

sub set_files{
    my $pid;
    ($frtag,$frh,$pid) = @_;
    my @fs=&get_lines($frh);
    unless(@fs){
	&mess("Reading file finished.\n");
	&end_files(0);
	unless(@files){
	    undef $stacom; # <<<<<<<<<<<
	    &switch_stack(2);
	}
	&clean_pids($pid);
	return FALSE;
    }
    foreach(@fs){
	my $f=$enc->decode($_);
	&echo("set_files: GOT: $f");
	if($f=~/^#FILE(.)?:(.*)/){
	    my $t=$1;
	    my $d=$2; # <<<<<<<<
	    my ($w,$h);
	    ($w,$h,$d)=$d=~/(\d+),(\d+),(.*)/ if(defined($t) && $t eq "I");
	    my $owh="$w:$h" if (defined($w) && defined($h));
	    ($d,$f)=&spfp($d);
	    my $i=&get_di($d);
	    &reg_file($i,$f,\@files,      \@stats,      undef,        $owh);
	    &reg_file($i,$f,$baks[0]->[1],$baks[0]->[2],$baks[0]->[3],$owh) if @baks;
	    &show_image(0) unless defined($kf); #|| defined($nfsorter));
	    if(@files<10 || @files % 10 == 0){
		&set_sbar;
		$win->window->process_all_updates;
	    }
	}elsif($f=~/^#TMPDIR:(.+)/){
	    push @tmpdirs, $1;
	}elsif($f=~/^#ARXV:(\d+):(.+)/){
	    push @arxvs, $2;
	}elsif($f=~/^#OPATH:(\d+):(.+)/){
	    $opath{$2}="$#arxvs"."."."$1";
	}elsif($f=~/^#PID:(\d+)/){
	    push @frpids, $1;
	}elsif($f=~/^#U1:(.+)/){
	    $avr_aspect=$avr_aspect?($avr_aspect+$1)/2:$1;
	}elsif($f=~/^#([A-Z]+):(.+)/){
	    &mess("$2\n",$1);
	}
    }
    return TRUE;
}

sub select_files{
    return if $frtag;
    my $mode=shift;
    my ($t,$m);

    if($mode eq "save"){
	$t="save";
	$m="Save file list as"
    }elsif($mode=~/dir/){
	$t = "select_folder";
	$m = "Select Directory"
    }else{
	$m = "Select files to " . ($mode =~ /add/ ? "Add":" Open");
	$t = "open";
    }
    my $dialog = Gtk2::FileChooserDialog->new(
	$m,
	undef,
	$t,
	'gtk-cancel' => 'cancel',
	'gtk-ok'     => 'ok',
        );
    if($mode ne "save"){
	my $imf = Gtk2::FileFilter->new();
	$imf->set_name("Image files");
	$imf->add_mime_type("image/*");
	$dialog->add_filter($imf);
	my $af = Gtk2::FileFilter->new();
	$af->set_name("Whatever files");
	$af->add_pattern("*");
	$dialog->add_filter($af);
	$dialog->set_select_multiple(1);
	$dialog->set_current_folder($last_visit) if defined $last_visit;
    }
    my @fs = $dialog->run eq "ok" ? $dialog->get_filenames:();
    $dialog->destroy;
    return $fs[0] if $mode eq "save";
    &start_load($mode,@fs) if @fs;
}

sub start_load{
    my $mode=shift;
    $mode=~s/:(.*)//;
    my $opt=$1;
    my @fs=@_;
    &rm_all unless  $mode=~/add/;
    ($last_visit=$fs[0])=~s/\/[^\/]+$//;
    my $nf=@fs;
    my $nk;
    foreach(@fs){
	if(-T $_ &&
	   (open my $fh, "<", $_)){
	    my $h=<$fh>;
	    close $fh;
	    if($h=~/^#!$0/){
		my @ffs;
		my @sss;
		if(my $k=&load_fl($_,\@ffs,\@sss)){
		    $nk=-$k+$#files if(defined($k)&&$k<0);
		    &add_files(0,@ffs);
		    @stats=(@stats,@sss);
		    &mess((@ffs)." file(s) loaded: $_\n","OK");
		    undef $_;
		    $nf--;
		}
	    }
	}
    }
    unless($nf){
	unless(@files){
	    # use the $kf in the flist only if there are no images
	    undef $kf;
	    show_image($nk);
	}
	return;
    }

    my $myopt;
    my ($x,$e,$d,$f,$r);
    if(defined($fck_com)){
	$fck_com=~/fck.pl (\d+)/;
	$myopt=$1;
	$e=substr($myopt,0,1);
	$d=substr($myopt,1,1);
	$f=substr($myopt,2,1);
	$r=substr($myopt,3,1);
	$x=0;
    }else{
	$myopt="1000";
	$x=1;
    }
    if(defined($opt)){
	for(my $k=0;$k<length($opt);$k++){
	    given(substr($opt,$k,1)){
		$x=1 when("x");
		$e=0 when("e");
		$d=1 when("d");
		$f=1 when("f");
		$r=1 when("r");
		$e=1 when("E");
		$d=0 when("D");
		$f=0 when("F");
		$r=0 when("R");
	    }
	}
    }
    if($x==1){
	&add_files(1,@_);
    }else{
	my $com;
	$myopt="$e$d$f$r";
	if(defined($fck_com)){
	    ($com=$fck_com)=~s/fck.pl \d+/fck.pl $myopt/;
	}else{
	    $com="$mydir/imv_fck.pl $myopt \"\" \"\"";
	}
	&add_watcher(\&set_files,"nice -n 10 $com ".&a2arg(@_));
    }
}

sub a2arg{
    my $a;
    foreach(@_){
	next unless defined $_;
	$a .= " '$_'";
    }
    return $enc->encode($a);
}

sub add_watcher{
# callback, command, nowait
    my $pcbf=$_[0];
    &mess("=> ".$enc->decode($_[1])."\n");
    my $pid=open my $fh, "-|", $_[1].($devm?"":" 2>/dev/null");
    goto FAILURE unless $pid;
    push @pids, $pid;
    my $flags=0;
    my $ok=fcntl($fh, F_GETFL, $flags);
    goto FAILURE unless $ok;
    $flags |= O_NONBLOCK;
    $ok=fcntl($fh, F_SETFL, $flags);
    goto FAILURE unless $ok;
    my $tag;
    $tag=Gtk2::Helper->add_watch(fileno($fh),
				 in => sub {
				     &$pcbf($tag,$fh,$pid);
				 });
    $win->window->set_cursor(Gtk2::Gdk::Cursor->new("watch")) unless $_[2];
    return 0;
  FAILURE:
    &mess("add_watcher failed for $_[1]\n","ERR");
    return $!;
}

sub print_flist{
    unless(@files){
	&mess("No files in the current stack\n","WARN");
	return;
    }
    my $fmt="%.".length(scalar @files)."d.%3.3d";
    my ($K,$M,$G,$T);
    if($_[0]){
	$K=1024;$M=$K*$K;$G=$M*$K;$T=$G*$K;
    }
    my $ns=0;
    my $a=defined($theA)?$theA:-1;
    my $b=defined($theB)?$theB:-1;
    ($a,$b)=($b,$a) if $a>$b;

    &mess("\n".(@files)." file".((@files)>1?"s":"").", $ns selected:\n");
    for(0..$#files){
	my $s="  ";
	if(substr($stats[$_],0,1)){
	    if($_==$kf){
		$s=">*";
	    }else{
		$s=" *";
	    }
	    $ns++;
	}elsif($_==$kf){
	    $s="> "
	}
	my $f;
	if($a==$_){
	    if($b==$_){
		$f= "|".$fmt."|";
	    }else{
		$f="/".$fmt."\\";
	    }
	}elsif($b==$_){
	    $f="\\".$fmt."/";
	}elsif($a>=0 && $b>=0 && $_>$a && $_<$b){
	    $f="|".$fmt."|";
	}else{
	    $f="[".$fmt."]";
	}
	$s.=sprintf $f, $_+1,(($stats[$_]=~$gidqr)?$1:0);
	my $uf = &get_fn($_);
	if($_[0]){
	    my ($i,$w,$h)=
		Gtk2::Gdk::Pixbuf->get_file_info($uf);
	    if(defined $i){
		my ($sec, $min, $hour, $mday, $mon, $year,
		    $wday, $yday, $isdst) = localtime((stat($uf))[9]);
		my $fs=(stat($uf))[7];
		my $fm;
		if($fs<$K){
		    $fm="B";
		}elsif($fs<$M){
		    $fm="%5.1fK";
		    $fs=$fs/$K;
		}elsif($fs<$G){
		    $fm="%5.1fM";
		    $fs=$fs/$M;
		}elsif($fs<$T){
		    $fm="%5.1fG";
		    $fs=$fs/$G;
		}else{
		    $fm="%fG";
		    $fs=$fs/$G;
		}
		$s.=(sprintf " %.4d-%.2d-%.2d %.2d:%.2d %4d x %4d $fm",
		     $year+1900,$mon,$mday,$hour,$min,$w,$h,$fs);
	    }else{
		$s.=" ???";
	    }
	}
	$s.=" $uf";
	my $bold=$_==$kf?"CUR":undef;
	my $col=substr($stats[$_],0,1)?"SEL":undef;
	&mess("$s\n",$col,$bold);
    }
    &mess((@files)." file".((@files)>1?"s":"").", $ns selected.\n");
}

sub dup_ufnc{
    #0: trget key_or_name
    #1: link key_or_name
    #2: do not encode
    my $getk=sub{
	my $k;
	if(substr($_[0],0,1) eq "'" && length($_[0])>1){
	    $k=substr($_[0],1);
	}elsif($_[0]=~/[\w\d-]+/){
	    $k=&enc_kb($_[0]);
	}else{
	    &mess("dup_ufnc: invalid key/name: $_[0]\n");
	    return;
	}
	unless(defined($k)){
	    &mess("dup_ufnc: failed to eval key/name from: $_[0]\n","ERR");
	    return;
	}
	return $k;
    };
    if($#_<1){
	&mess("dup_ufnc: too few arguments\n","ERR");
	return;
    }
    my $kt=$_[2]?$_[0]:$getk->($_[0]);
    return unless defined $kt;
    my $kl=$_[2]?$_[1]:$getk->($_[1]);
    return unless defined $kl;
    if($kt eq $kl){
	&mess("dup_ufnc: same target and link name: $_[0]\n","ERR");
	return;
    }
    unless(exists($ufncs{$kt})){
	&mess("dup_ufnc: no such key/name has been set: $_[0]\n","ERR");
	return;
    }
    my $p=$ufncs{$kt}->[0];
    &mess("$$p assigned as $_[1]\n");
    $ufncs{$kl}=[$ufncs{$kt}->[0],$ufncs{$kt}->[1]];
}

sub getf{
    return undef unless defined $_[0];
    if(open my $fh,"<",$_[0]){
	my $c=do{local $/;<$fh>;};
	close $fh;
	return $c;
    }else{
	&mess("getf: opening \'$_[0]\' failed\n","ERR");
    }
    return undef;
}

sub eval_str{
    my ($code,$name)=@_;
    return undef unless defined $code;
    my $p=eval $code;
    if($@){
	&mess("eval_str:"
	      .(defined($name)?" failed to eval '$name':":"")
	      ."\n$@","ERR");
	return undef;
    }
    return $p;
}

sub get_scrpath{
    return $_[0] if -f $_[0];
    my $f=$_[0];
    if($f=~/^\// || $f=~/^\./ || ! -f ($f="$scrdir/$_[0]")){
	&mess("get_scrpath: file not found: '$_[0]'\n","ERR");
	return undef;
    }
    return $f;
}

sub set_ufnc{
    #0: filename
    #1: key or 'name
    #2: 0/undef=do not run, 1=run, 2=run and return
    #3-$#: args
    #RC: 1 on success
    if($#_<0){
	&mess("set_ufnc: too few arguments\n","ERR");
	return;
    }
    my $myname=$_[0];
    my $f=&get_scrpath($myname);
    return unless defined $f;
    my $c=&getf($f);
    return unless defined $c;
    my $p=&eval_str("sub{$c}",$f);
    return unless defined $p;

    my $rc=$p->(@_[3..$#_]) if defined $_[2];
    return $rc if defined($_[2]) && $_[2]==2;
    my $k;
    my @ks;
    if(defined($_[1])){
	given($_[1]){
	    when(/^_./){
		$k=$_;
	    }
	    when(/^'\D/){
		$k=substr($_,1);
	    }
	    default{
		if($_[1]=~/[\w\d-]+/){ #<<<
		    $k=&enc_kb($_[1]);
		}else{
		    &mess("set_ufnc: invalid key/name: $_[1]\n");
		    return;
		}
	    }
	}
    }else{
	while($c=~/(^|\n)\s*#KEY:(.+)\n/g){
	    $k=&enc_kb($2);
	    if(defined($k)){
		push @ks, $k;
	    }else{
		return;
	    }
	}
	while($c=~/(^|\n)\s*#NAME:(.+)\n/g){
	    my $n=$2;
	    $n=~s/\s//g;
	    push @ks, $n;
	}
	unless(@ks){
	    &mess("set_ufnc: no key/name given\n","ERR");
	    return;
	}
	$k=$ks[0];
    }
    if(defined($k)){
	$ufncs{$k}=[\$f,$p];
	&mess("$f loaded as $k\n");
	if($#ks>=1){
	    foreach(@ks[1..$#ks]){
		return unless &dup_ufnc($k,$_,1);
	    }
	}
	given($k){
	    when("_sbarstr"){
		$psbarstr_def=$psbarstr unless $psbarstr_def;
		$psbarstr=$ufncs{$_}->[1];
	    }
	}
	return 1;
    }else{
	&mess("set_ufnc: failed to get key/name\n","ERR");
	return;
    }
}

sub get_modestr{
    my $sm;
    for(reverse(@baks)){
	$sm.=$_->[0];
    }
    return (defined $crec?"REC ".($crec>=0?$crec+1:-$crec)."| ":"")
	.(defined $keyin ?"> $keyin | ":"")
	.($sm?"$sm| ":"")
}

sub set_sbarstr{
    my $m;
    $sbmode=0 if $sbmode>2;
    given($sbmode){
	when(1){
	    if(defined($kf)){
		$m=$files[$kf];
		$m=~s/$didqr//;
	    }
	}
	when(2){
	    $m=&get_fn($kf) if defined $kf;
	}
    }

    if($sbmode != 0){
	$m="[No files]" unless defined $m;
	return $m;
    }

    my $t =
	($cmode==0?($lurd[0]||$lurd[1]||$lurd[2]||$lurd[3]?"c":"")
	 :($cmode==1?"(*c)":"(c*)")
	 .":$lurd[0],$lurd[1],$lurd[2],$lurd[3]")
	.($mirrored?"m":"")
	.($flipped?"v":"")
	.($rot?"r$rot":"");
    my $k =
	($keep_crop?"c":"")
	.($keep_flips?"m":"")
	.($keep_rot?"r":"")
	.($keep_scale?"x":"")
	.($keep_size?"s":"")
	.($keep_pos?"p":"")
	.($keep_orient?"o":"");
    my $o = ($leftright?">":"<").($updown?"v":"^");
    $o = reverse($o) if ( $page_scan % 2 == 0 );
    my $ab=(defined($theA) || defined($theB))?
	(defined($theA)?$theA+1:"?")
	."->"
	.(defined($theB)?$theB+1:"?")
	." "
	:"";
    $ab = " ".$ab if $ab;
    my $g=$1 if(defined($kf)&&$stats[$kf]=~$gidqr);

    $m =($rep?"<x".$rep."> ":"")
	.&get_modestr
	.(@files?(defined($kf)&&substr($stats[$kf],0,1)?"*":"")
	  ."[".(defined($kf)?$kf+1:0)."/".(@files)
	  .($nselect>0?":$nselect":"")."]"
	  .($g?"\@$g ":" ")
	  .($pixbak?(($pixbak->get_width)."x".($pixbak->get_height)):"")
	  .($scale!=1&&$scale>0?(sprintf "(x%.2f)",$scale):"")
	  :"[No files]")
	.($t ne ""?" T:$t":"")
	.((($t||$k||$scale!=1)&&$pix)?((" =".$pix->get_width)."x".($pix->get_height)):"")
	.($k ne ""?" K:$k":"")
	.($fit ne ""?" F:$fit":"")
	." o:$o"
	.($page_scan>0?" S:On":"")
	.($init_pos?" R:On":"")
	.(" P:$dpx"."x$dpx_boost,"
	  .($psidx>0?$psidx:"1/".abs($psidx)))
	.$ab
	.(defined($player)?" Play $delay ms":"")
	.($animate?" A:".($set_ani?"On":"Off"):"");
}

#$#sub load_hooks{
#$#    foreach(glob($hokdir."/*")){
#$#	given($_){
#$#	    when(/key_press_event_1.pl$/){
#$#		$HK_key_press_event_1=&getf($_);
#$#	    }
#$#	}
#$#    }
#$#}

sub start{
    #
    my @fs;
    my @fls;
    my $precom;
    #
    my $allow_dotfile;
    my $use_file_cmd;
    my $no_conv;
    my $no_arxv;
    my $no_ck;
    my $copt;
    #
    my $always_allow_dotfile;
    my $always_use_file_cmd;
    my $never_conv;
    my $never_arxv;
    my $never_ck;
    my $win_dec;
    my $arxvpass;
    my $ipc;
    #
    my $obj;
    my $bgcol;
    my $opt;
    my $fonts,
    my $logf;
    my $sockpath;
    my $with_snd;
    my $sorter4a;
    my $fck;
    my $optend;
    my $rcf;
    my $neg;
    #
    my $pruneval=sub{
	return unless -r $_[0];
	eval &getf($_[0]);
	if($@){
	    &echo("#ERR:eval failed: $_[0]\n$@");
	    exit 1;
	}

    };

    if(@ARGV && $ARGV[0] eq "-z"){
	shift @ARGV;
    }else{
	$pruneval->($ENV{HOME}."/.imvrc");
	&echo(".imvrc loaded.");
    }

    foreach(@ARGV){
	$_=$enc->decode($_);
	if(defined $obj){
	    $$obj=$_;
	    undef $obj;
	    next;
	}
	if($optend){
	    push @fs, $_;
	}elsif($_ eq "-"){
	    while(defined(my $f=<STDIN>)){
		chomp($f);
		push @fs,$f;
	    }
	}elsif($_=~/^-(.+)/){
            my $a=$1;
	    $neg=0;
            for(my $k=0;$k<length($a);$k++){
		given($opt=substr($a,$k,1)){
		    #
		    when("^"){$neg=1;}
		    #
		    when("d"){$allow_dotfile=$neg?0:1;}
		    when("e"){$use_file_cmd=$neg?1:0;}
		    when("r"){$no_conv=$neg?0:1;}
		    when("f"){$no_arxv=$neg?0:1;}
		    when("x"){$no_ck=$neg?0:1;}
		    #
                    when("D"){$always_allow_dotfile=$neg?0:1;}
		    when("E"){$always_use_file_cmd=$neg?1:0;}
		    when("R"){$never_conv=$neg?0:1;}
		    when("F"){$never_arxv=$neg?0:1;}
		    when("X"){$never_ck=$neg?0:1;}
		    #
		    when("W"){$win_dec=$neg?TRUE:FALSE;}
		    when("l"){$obj=\$fls[@fls];}
		    when("T"){$obj=\$sdelay;}
		    when("t"){$obj=\$delay;}
		    when("u"){$obj=\$tdelay;}
		    when("b"){$obj=\$bgcol;}
		    when("B"){$obj=\$sbg;}
		    when("s"){$obj=\$nfsorter;}
		    when("S"){$sorter4a=1;$obj=\$nfsorter;}
		    when("A"){$with_snd=$neg?0:1;}
		    when("j"){$obj=\$fonts;}
		    when("g"){$obj=\$copt;}
		    when("C"){$obj=\$ecom;}
		    when("a"){$obj=\$rcf;}
		    when("p"){$obj=\$arxvpass;}
		    when("k"){$obj=\$sockpath;}
		    when("n"){$ipc=$neg?1:0;}
		    when("z"){&echo("#ERR:must be at the beginning: $_");exit 1;}
		    when("G"){$devm=$neg?0:1;}
		    when("h"){
			require "$mydir/imv_man.pm";
			my $m=&MAN::show_pod_man($0);
			&echo("#ERR:$m") if defined $m;
			exit;
		    }
		    when("v"){&echo("#MSG:"."$me version $VERSION"); exit};
		    when("i"){$optend=1;}
		    default{&echo("#ERR:unknown option: $_");exit 1;}
		}
	    }
	}elsif($_=~/^\+\+(.+)/){
             $stacom.=":".$1;
	}elsif($_=~/^\+(.+)/){
             $precom.=":".$1;
	}else{
	    push @fs, $_;
	}
    }
    if(defined $obj){
	&echo("#ERR:no argument passed for option: $opt");
	exit 1;
    }

    # cmdline parse end

    $pruneval->($rcf) if defined $rcf;

    $allow_dotfile = $always_allow_dotfile if defined $always_allow_dotfile;
    $use_file_cmd  = $always_use_file_cmd  if defined $always_use_file_cmd;
    $no_conv       = $never_conv           if defined $never_conv;
    $no_arxv       = $never_arxv           if defined $never_arxv;
    $no_ck	   = $never_ck             if defined $never_ck;

    $allow_dotfile        = 0    unless defined $allow_dotfile;
    $use_file_cmd         = 1    unless defined $use_file_cmd;
    $no_conv              = 0    unless defined $no_conv;
    $no_arxv              = 0    unless defined $no_arxv;
    $no_ck	          = 0    unless defined $no_ck;
    $copt                 = '""' unless defined $copt;
    $always_allow_dotfile = 0    unless defined $always_allow_dotfile;
    $always_use_file_cmd  = 1    unless defined $always_use_file_cmd;
    $never_conv           = 0    unless defined $never_conv;
    $never_arxv           = 0    unless defined $never_arxv;
    $never_ck             = 0    unless defined $never_ck;
    $win_dec              = TRUE unless defined $win_dec;
    $arxvpass             = '""' unless defined $arxvpass;
    $ipc                  = 1    unless defined $ipc;

    $psbarstr=\&set_sbarstr unless defined $psbarstr;
    $sdelay=1 if $sdelay<=0;
    $tdelay=1 if $tdelay<=0;
    $fck="$mydir/imv_fck.pl $use_file_cmd$allow_dotfile$no_arxv$no_conv $copt $arxvpass" unless $no_ck||defined $fck;
    $fck_com="$mydir/imv_fck.pl $always_use_file_cmd$always_allow_dotfile$never_arxv$never_conv $copt $arxvpass" unless $never_ck || defined $fck_com;
    if($ipc){
	$ipc="$mydir/imv_ipc.pl in";
	$ipc.=" $sockpath" if defined $sockpath;
    }
    $rmdir_com="$mydir/imv_rmdir.pl";
    undef $sbg if defined $sbg && $sbg eq "";
    $sbg="black" unless defined $sbg;
    undef $bgcol if defined $bgcol && $bgcol eq "";
    ($bgcol,$tnvbgc,$tnvsbg)=split(/,/,$bgcol) if(defined $bgcol && $bgcol=~/,/);
    undef $fonts if defined $fonts && $fonts eq "";
    ($logf,$tnvf)=split(/,/,$fonts) if defined $fonts;
    $pixdir="$mydir/resources/pix";
    $snddir="$mydir/resources/snd";
    $scrdir="$mydir/extensions";
    $hokdir="$mydir/extensions/hooks";
    undef $nfsorter if defined $nfsorter && $nfsorter eq "";
    if(defined($nfsorter)){
	if($nfsorter=~/^\w[-+]?$/){
	    $nfsorter="+".$nfsorter if $sorter4a;
	}else{
	    &echo("#WARN:unrecognized sorter: $nfsorter");
	    undef $nfsorter;
	}
    }
    undef $ecom if defined $ecom && $ecom eq "";

    &show_main($win_dec,$bgcol,$logf); undef &show_main;

#$#    &load_hooks();

    my $nk;
    if(@fls){
	foreach(@fls){
	    my @ffs;
	    my @sss;
	    if(my $k=&load_fl($_,\@ffs,\@sss)){
		$nk=-$k+$#files if defined($k)&&$k<0&&!defined($nk);
		&add_files(0,@ffs);
		@stats=(@stats,@sss);
		&mess((@ffs)." file(s) loaded: $_\n","OK");
	    }else{
		&mess("failed to load flist: $_\n","ERR");
	    }
	}
	$no_ck=1 unless @fs;
	# if @fs are not given, $kf is set by the first flist,
        # otherwise $kf will will be set to 0.
    }
    &start_keysim($precom) if defined $precom;
    unless($no_ck){
	if(@fs){
	    &smess("Loading files...");
	    &add_watcher(\&set_files,
			 "nice -n 10 $fck ".&a2arg(@fs));
	}#else run $stacom
    }else{
	foreach(@fs){
	    unless(-f $_){
		&mess("rejected: $_","WARN");
		undef $_;
	    }
	}
	&add_files(1,@fs) if @fs;
	if(defined($nfsorter) && @files){
	    &sort_f($nfsorter);
	    undef $nfsorter if substr($nfsorter,0,1) ne "+";
	}
	Glib::Idle->add(\&show_image,$nk=0) if @files;
	undef @fs; #<<<< run $stacom
    }
    &start_keysim($stacom,1) if(defined($stacom)&&!@fs);
    &with_snd if $with_snd;
    &add_watcher(\&proc_ipc,
		 "nice -n 10 $ipc",
		 1) if $ipc;
}

sub end_ipc{
    if($ipcspid && kill(0,$ipcspid)){
	kill 'TERM', $ipcspid;
	&mess("sent kill signal to $ipcspid connected to $sockname\n");
    }
    close($ipch) if defined $ipch;
    Gtk2::Helper->remove_watch($ipctag)if defined $ipctag;
    unlink $sockname if -e $sockname;
    undef $ipch; undef $ipctag; undef $ipcspid; undef $sockname;
}

sub parse_add_files{
    my $opt=shift;
    my $myopt;
    my ($x,$e,$d,$f,$r);
    if(defined($fck_com)){
	$fck_com=~/fck.pl (\d+)/;
	$myopt=$1;
	$e=substr($myopt,0,1);
	$d=substr($myopt,1,1);
	$f=substr($myopt,2,1);
	$r=substr($myopt,3,1);
	$x=0;
    }else{
	$myopt="1000";
	$x=1;
    }
    if(defined($opt)){
	for(my $k=0;$k<length($opt);$k++){
	    given(substr($opt,$k,1)){
		$x=1 when("x");
		$e=0 when("e");
		$d=1 when("d");
		$f=1 when("f");
		$r=1 when("r");
		$e=1 when("E");
		$d=0 when("D");
		$f=0 when("F");
		$r=0 when("R");
	    }
	}
    }

}

sub switch_stdout{
    if(defined($_[0])&&!defined($mystdout)){
	# save
	open $mystdout,">&",\*STDOUT;
	open $mystderr,">&",\*STDERR;
    }
    close STDERR;
    if(defined($_[0])){
	open STDOUT,">",$_[0];
	open STDERR,">",$_[0];
    }else{
	# restore
	open STDOUT, ">&", $mystdout;
	open STDERR, ">&", $mystderr;
    }
}

sub proc_ipc{
    my $pid;
    ($ipctag,$ipch,$pid)=@_;
    my @ms=&get_lines($ipch);
    unless(@ms){
	&mess("IPC server exited\n");
	&end_ipc;
	&clean_pids($pid);
	return FALSE;
    }
    foreach(@ms){
	my $m=$_;
	chomp($m);
	$m=$enc->decode($m);
	&echo("proc_ipc: GOT: $m");
	if($m=~/^#IPCIN:(.+)/){
	    my $a=$1;
	    if($a=~/^#TTY:([^:]+):(.+)/){
		$a=$2;
		if(!defined($echo_tty) || $1 ne $echo_tty){
		    if(-w $1){
			$echo_tty=$1;
			&switch_stdout($echo_tty);
		    }
		}
	    }
	    if($a=~/^\cO(.+)/){
		push @fnbuf, $1;
	    }elsif($a=~/^\cZ(.*)/){
		if(@fnbuf){
		    my @b=@fnbuf;
		    undef @fnbuf;
		    my $m="add".(defined($1)?":$1":""); # <<<<<<<<<<<<<
		    &start_load($m,@b);
		}
	    }else{
		&start_keysim($a);
	    }
	}elsif($m=~/^#PID:(\d+)/){
	    $ipcspid=$1;
	    &mess("IPC server running with pid=".$ipcspid."\n");
	}elsif($m=~/^#SOCKNAME:(.+)/){
	    $sockname=$1;
	    &mess("IPC server opened for ".$sockname."\n");
	}
    }
    return TRUE;
}

sub save_fl{
#    my ($fp,$save_selected)=@_;
    return if($_[1]&&$nselect==0);
    if(open(my $fh, ">", $_[0])){
	print $fh "#!$0 -l\n#VER:$VERSION\n";
	print $fh "#U1:$avr_aspect\n" if $avr_aspect; #<<<<<<<< wrong for selected images
	my @dls;
	my $a=0;
	my @tar;
      FILE: foreach(@files){
	  next if($_[1]&&!substr($stats[$_],0,1));
	  $_=~$didqr;
	  my $j=$1;
	  unless(defined($dls[$j])){
	      if(%opath){
		  foreach my $p (keys(%opath)){
		      if($dirs[$j]=~/^$p/){
			  my $ka=$opath{$p};
			  my $kb;
			  $ka=~/^(\d+)\.(\d+)$/;
			  $ka=$1; $kb=$2;
			  unless(defined($tar[$ka])){
			      my $ax=$arxvs[$ka];
			      if($ax=~/^(\d+):/){
				  my $ra=$1;
				  unless(defined($tar[$ra])){
				      $a++;
				      $tar[$ra]=$a;
				      print $fh "#ARXV:$a:$arxvs[$ra]\n";
				  }
				  $ax=~s/^(\d+)/$tar[$ra]\.$kb/;
			      }
			      $a++;
			      print $fh "#ARXV:$a:$ax\n";
			      $tar[$ka]=$a;
			  }
			  $dls[$j]=$dirs[$j];
			  $dls[$j]=~s/^$p(.*)/$tar[$ka]:$1\//;
			  next FILE;
		      }
		  }
	      }
	      $dls[$j]=$dirs[$j]."/";
	  }
      }
	print $fh "#FCK:$fck_com\n" if $a;
	my $d=0;
	for(my $k=0;$k<=$#dls;$k++){
	    next unless defined $dls[$k];
	    $d++;
	    print $fh "#DIR:$d:",$enc->encode($dls[$k])."\n";
	    $dls[$k]=$d;
	}
	my $k=0;
	foreach(0..$#files){
	    next if($_[1]&&!substr($stats[$_],0,1));
	    $k++;
	    print $fh "#KF:$k\n" if $kf==$_;
	    my $f=$files[$_];
	    $f=~s/$didqr/$dls[$1]:/;
	    print $fh $stats[$_]."\t".$enc->encode($f),"\n";
	}
	close($fh);
	my $s=$_[1]?"selected":"all";
	$tmpmsg="saved for $s: $_[0]";
	&mess("flist written for $s: $_[0]\n","OK");
	return 1;
    }else{
	$tmpmsg="Error writing: $_[0]";
	&mess("failed to save filelist: $_[0]\n","ERR");
    }
}

sub load_fl{
    my ($fp,$pfs,$pss)=@_;
    return unless open(my $fh, "<", $fp);
    my $fl=do{local $/;<$fh>;};
    close $fh;
    $avr_aspect=$avr_aspect?($avr_aspect+$1)/2:$1 if($fl=~/^#U1:(.*)/&&$1);
    my @dls;
    my @ops;
    while($fl=~/#(ARXV|DIR):(\d+):(\N+)/g){
	$dls[$2]=$enc->decode($3);
	if($1 eq "ARXV"){
	    my $i=$2;
	    open my $fh, "$fck_com ... '$dls[$i]'|"; # if err
	    my $s=do{local $/;<$fh>;};
	    close $fh;
	    my $opk=-1;
	    while($s=~/#(TMPDIR|OPATH):(\N+)/g){
		if($1 eq "TMPDIR"){
		    my $e;
		    if(@tmpdirs){
			foreach(@tmpdirs){
			    if($_ eq $2){
				$e=1;
				last;
			    }
			}
		    }
		    push @tmpdirs, $1 unless $e;
		}else{
		    $opk++;
		    $ops[$i][$opk]=$2
		}
	    }
	}
    }
    my $k=-1;
    while($fl=~/(\N+)\t(\N+)\n/g){
	my $s=$1;
	my $f=$2;
	$f=~/$didqr/;
	($f=$enc->decode($f))=~s/$didqr/$dls[$1]\//;
	$k++;
	$pss->[$k]=$s;
	$pfs->[$k]=$f;
	substr($pss->[$k],0,1,"0");
    }
    return -$1 if $fl=~/#KF:(\d+)/;
    return 1;
}

sub start_keysim{
# syntax:
# key:key:key
# Key!&com&key
# key
    my ($keys,$del)=@_;
    if(defined($kscbid)){
	&mess("start_keysim busy when given: $_[0]","WARN");
    }
    &set_ksq($keys);
    $kscbid||=Glib::Timeout->add(1,\&keysim);
#    $kscbid||=Glib::Idle->add(\&keysim);
    undef $keys if defined $del;
}

sub set_ksq{
# syntax:
# ks  = str:str
# str = key or com
# com = !DstrD
    my $ks=$_[0];
    $ks=~s/^://;
    $ks=~s/:*$//;
    my $len=length($ks);
    my $pset=sub{
	my ($k1,$k2)=@_;
	my $key=($k2>=$k1)?substr($ks,$k1,$k2-$k1+1):"";
	push @ksqs, $key;};
    my $k=-1;
    my $p1=-1;
    my $q1=-1;
    my $q2;
    my $d;
    while($k<$len){
	my $c;
	$k++;
	unless($k==$len){
	    $c=substr($ks,$k,1);
	}else{
	    $c=defined($d)?$d:":";
	}
	if(defined($d)){
	    if($c eq $d){
		$q2=$k;
		$pset->($q1,$q2-1);
		undef $d;
		$p1=$k;
	    }
	    next;
	}
	given($c){
	    when(":"){
		unless(defined($q2)&&$k==$q2+1){ # ignore !#a#:
		    $pset->($p1+1,$k-1) if defined $p1;
		}
		$p1=$k;
	    }
	    when(["!","/"]){
		unless($k==0){ # a!
		    $pset->($p1+1,$k-1) unless $p1==$k-1;
		    undef $p1;
		}
		if($k<$len-1){
		    $k++;
		    $d=substr($ks,$k,1,$c);
		    $q1=$k;
		    undef $q2;
		}else{
		    $pset->($k,$k);
		}
	    }
	}
    }
}

sub keysim{
    return TRUE if $kpe_busy;
    Gtk2->main_iteration while Gtk2->events_pending;
    $wksqid++;
    if($wksqid>$#ksqs){
	undef @ksqs;
	$wksqid=-1;
	undef $kscbid;
	&echo("closing keysim");
	if(defined($echo_tty)){ # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
	    undef $echo_tty;
	    &switch_stdout();
	}
	return FALSE;
    }
    my $key=$ksqs[$wksqid];
    if(defined($rep)){
	if($rep>1){
	    my @a=($key) x ($rep-1);
	    push @a, $ksqs[$wksqid+1..$#ksqs] if $wksqid<$#ksqs;
	    @ksqs=@a;
	    $wksqid=-1;
	}
	undef $rep;
	$pproc_key_x=\&proc_key_1; # <<<<<<<<<<<< what about sbar?
    }
    &echo("keysim: #$wksqid: $key");
    if($key eq ""){ # should work as a wait
	Glib::Timeout->add(500,sub{$kscbid=Glib::Idle->add(\&keysim);return FALSE;});
	return FALSE;
    }
    given(substr($key,0,1)){
	when("/"){
	    &open_find unless $findb->visible;
	    $findw->set_text(substr($key,1));
	    return TRUE;
	}
	when("!"){
	    &open_com unless $comb->visible;
	    $comw->set_text(substr($key,1));
	    &do_com();
	    return TRUE;
	}
	when("@"){
	    my $f=`sh -c "echo $key"`;
	    chomp($f);
	    $f=$enc->decode($f);
	    if(open my $fh, "<", $f){
		my $c=do{local $/;<$fh>;};
		close $fh;
		$c=~s/\s*#.*\n/\n/g;
		$c=~s/^\s*\n//g;
		$c=~s/\n(\N+)/:$1/g;
		$c=~s/[[:cntrl:]]//g;
		&start_keysim($c);
	    }else{
		&echo("#WARN:failed to open: ".$f);
	    }
	    return TRUE;
	}
    }
    if($key eq "Tab"){
	if($current_stack==1){
	    $img->grab_focus;
	}else{
	    $logw->grab_focus;
	}
	return TRUE;
    }
    my $kst=($key=~s/^=//);
    my $ksz=($key=~s/^~//);
    ($shft,$ctrl,$meta)=(0,0,0);
    if(!$kst&&!$ksz){
	my ($S,$C,$M)=(0,0,0);
	if($key=~s/^([SCM]+)-//){
	    my $x=$1;
	    $S=1 if($x=~/S/);
	    $C=1 if($x=~/C/);
	    $M=1 if($x=~/M/);
	    $key=uc($key) if(length($key)==1&&$S&&$key=~/[a-z]/);
	}elsif($key=~s/^,?([-\+]?\d+[-\d.,+]*)//){
	    if(length($key)>=1){
		# possibly keyin com
		$keyin=$1;
	    }else{
		$keyin.=$1;
	    }
	    &echo("keysim: $keyin: $1,$key");
	}
	return TRUE if(!defined($key)||length($key)==0);
	$S=1 if length($key)==1&&$key=~/[A-Z]/;
	my $kv=$Gtk2::Gdk::Keysyms{$key};
	if(defined $kv){
	    $recs[$crec].=":$keyin" if(defined($crec)&&defined($keyin));
	    &proc_key($kv,$S,$C,$M,1);
	}else{
	    &echo("#WARN:invalid keyname: $key");
	}
    }else{
	my $pw;
	if($findb->visible&&$comb->visible){
	    if($comw->has_focus){
		$pw=\$comw;
	    }elsif($findw->has_focus){
		$pw=\$findw;
	    }else{
		for(my $i=$#opened;$i>=0;$i--){
		    if($opened[$i]==\&close_com){
			$pw=\$comw;
			last;
		    }elsif($opened[$i]==\&close_find){
			$pw=\$findw;
			last;
		    }
		}
	    }
	}elsif($findb->visible){
	    $pw=\$findw;
	}elsif($comb->visible){
	    $pw=\$comw;
	}
	unless(defined($pw)){
	    &echo("#WARN:no widget ready for text: $key");
	    return TRUE;
	}
	my $p=$$pw->get("cursor-position");
	my $t=$$pw->get_text;
	my $l=length($t);
	if($kst){
	    my $s=$key;
	    my $i=$wksqid;
	    while(1){
		last if $wksqid>=$#ksqs;
		$i++;
		my $k=$ksqs[$i];
		if(substr($k,0,1) eq "="){
		    $s.=substr($k,1);
		    $wksqid++;
		}else{
		    last;
		}
	    }
	    if($p==$l){
		$$pw->append_text($s);
		$$pw->set_position($l+length($s));
	    }elsif($p==0){
		$$pw->prepend_text($s);
		$$pw->set_position(length($s));
	    }else{
		$$pw->set_text(substr($t,0,$p).$s.substr($t,$p));
		$$pw->set_position($p+length($s));
	    }
	    if(defined $crec){
		$recs[$crec].=":=$s";
	    }
	}else{
	    given($key){
		when(0xff08){ # BS
		    if($p>0){
			$t=($p>1?substr($t,0,$p-1):"").($l>$p?substr($t,$p,$l-$p):"");
			$$pw->set_text($t);
			$$pw->set_position($p-1);
		    }
		}
		when(0xffff){ # DEL
		    if($l>$p+1){
			$t=($p>=1?substr($t,0,$p):"").substr($t,$p+1,$l-$p-1);
			$$pw->set_text($t);
		    }
		}
		when(0xff51){ # Left
		    $$pw->set_position($p-1) if $p>0;
		}
		when(0xff52){ # Up
		}
		when(0xff53){ # Right
		    $$pw->set_position($p+1);
		}
		when(0xff54){ # Down
		}
	    }
	}
    }
    return TRUE;
}

sub can_animate{
    open my $h, "<", $_[0] or return 0;
    binmode $h;
    my $s;
    read $h,$s,6;
    close $h;
    return ($s eq "GIF89a")?1:0;
}

=pod

=head1 NAME

imv - image viewer

=head1 USAGE

imv [options] files...

=head2 Files

=over 4

=item * image files: jpg, png, etc that F<gtk-query-immodules-2.0> can
handle.

=item * archive files: zip, rar, tar.gz, tar.bz2, tar.xz.

=item * ps, eps and pdf files that F<gs> can convert to png file.

=item * directories.

=item * '-' to read files from stdin.

=back

=head2 Options

Some of the these settings by options can also be set by the start-up
scripts, I<.imvrc> at $HOME directory and that of specified by the
B<-a> option. The target variables are set in the following order: 1)
in I<.imvrc> 2) by options and 3) in the script specified by B<-a>
I<file>.

B<^> before the option names makes the negation of the logical
options.

=over 4

=item B<-z>

Do not read I<.imvrc> at user's home directory. By default .imvrc will
be read just as B<-a> I<.imvrc> were passed. This option must be the
first argument in the command line.

=item B<-i>

Marks the end of options: the following arguments are filenames.

=item B<-s, -S> I<sort_by>

Set sorter used when reading files. I<sort_by> can be one of the
following characters: "z", "n", "t", "s", "x" and "R" for sort by full
path, base filename, file time, file size, and image size,
respectively and may end with "-" for sorting in descending
order. B<-s> for start-up only, B<-S> for whole session.

=item B<-l> I<file>

Read a I<file> as a filelist.

=item B<-r, -R>

Disables conversion of unsupported files to png on reading. The
conversion is enabled by default for ps, eps, pdf and some other
supported formats such as xcf, unless B<-x> option is given. B<-r> for
start-up only, B<-R> for whole session.

=item B<-f, -F>

Ignore archive files. B<-f> for start-up only, B<-F> for whole
session.

=item B<-d, -D>

Allow dot-files/directories to be scanned and loaded. B<-d> for
start-up only, B<-D> for whole session.

=item B<-e, -E>

Identify files by the extension. B<-e> for start-up only, B<-E> for whole
session.

=item B<-x, -X>

Skip file scan process for speed. It also disables reading of
directories and archives, and possible conversions. B<-x> for start-up
only, B<-X> for whole session.

=item B<-g> I<options>

Set options for gs of the converter. Default:
    "-q -dSAFER -dNOPAUSE -dBATCH
     -dTextAlphaBits=4 -dGraphicsAlphaBits=4
     -r200
     -sDevice=pngdev"

=item B<-p> I<password>

Set password for unzip or unrar.

=item B<-C> I<command>

Set shell command to be executed when C<^e> is pressed. For example,
C<-C "mv %_s:q ~/images">
will move the selected images to C<~/images>. See C<Shell> section of
the help window for available arguments (e.g., run C<imv +question>).

=item B<-A>

Enable sound effect.

=item B<-j> I<font,font>

Specify font for log window and osd of thumbnail viewer. If the length
of I<font> is 1, I<Monospace 11> will be set.

=item B<-t>

Set interval of page scroll in ms. Default: 1000.

=item B<-T>

Set interval of slide show in sec. Default: 5.

=item B<-u>

Set transition time of slide show in ms. Default: 120.
If 0, transition will be disabled.

=item B<-W>

Disables window decorations.

=item B<-b> I<color,color,color>

Sets background colors for main window, thumbnail pane/window, and
selected thumbnails. Unrecognized names will simply be ignored.  B<Note
that this option also enables mouse event handler (by design)>.

=item B<-B> I<colors>

Comma separated list of colors used in creating linear gradient
pattern for background of slideshow. The direction of the pattern can
be specified with the following prefixes: C<v:>, C<h:>, C<d:> and
C<r:> for vertical(default), horizontal, diagonal and another
diagonal, respectively. If one of the given color is not recognized,
the first valid color is used as solid pattern if any, otherwise
filled with black(default). The word "rainbow" is interpreted as
"violet,navy,blue,green,yellow,orange,red".

=item B<-n>

Disables IPC functions.

=item B<-k> I<socket>

Specify a socket path for IPC.

=item B<+[+]>I<cmd>

Colon separated list of commands to be executed on
start-up. B<++>I<cmd> will be executed AFTER loading files. If the
command is blank, that is, C<::> will work as a sleep for about 500
ms. The command must be one of the following types:

1) Name of a key; C<S->, C<C-> and C<M-> can be used as prefix for
shift, control and meta keys, respectively. If begins with [0-9], the
command will be passed with the numbers to invoke
numkey-commands. C<Tab> makes the main window grab focus.

2) Name of a text file with preceding C<@> to be read as a command list;
the file contains one command in a line.

3) Text strings to set in the find files or shell command box; it must
begin with C</> or C<!> and a character which can be used to mark the end
of strings.

For example, "++/|\.gif$|:Tab:CS-k:Escape" will remove gif files from
the list and "+640,400S" sets window size to 640x400.

=item B<-a> I<file>

Specify a file containing perl script to be executed right before
setting main window and reading files. The script will be evaluated
and run in the main:: context and can access any subroutines and
variables. At present, this is only useful for developers. Here is an
example of valid script:

  $dpx=1;         # pixel size for scroll by arrow-key
  $dpx_boost=10;  # multiplication factor for $dpx with shift-key
  $fit="f";       # fit image to window
  $defw=1024;     # default window width
  $defh=600;      # default window height

Note that the script must be written as a subroutine body.

=item B<-G>

Developer mode.

=back

=head1 Environment Variables

=over 4

=item B<IMVSOCKDIR>

Directory in which IPC socket will be created.

=item B<IMVTMPDIR>

Temporary directory for extracting archives.

=item B<IMVTERM>

Terminal emulator to browse docs/* files. Default: "xterm -e".

=item B<IMVPAGER>

Pager to print help massages etc. Default: "less -s".

=back

=head1 BUGs and all

=over 4

=item * Changing window size in "fit image mode" with thumbnail pane might
occasionally show scroll bar. This is partly because I don't know how
to get the "handle-size" of the thumbnail pane.

=item * It can happen that pausing in slideshow shows next image when the
slide fade-in/out is enabled. This is because there are some ms
between pix set and the start of fade-in/out.

=back

=head1 COPYRIGHT

Copyleft (!c) 2013-2014 Elseif Laboratory. All rights reversed.

This software is provided to you ``AS IS'' and any express or
implied warranties, including, but not limited to, the implied
warranties of merchantability and fitness for a particular purpose
are disclaimed.  In no event shall the developers or contributors be
liable for any direct, indirect, incidental, special, exemplary, or
consequential damages (ry

=cut
